0

I'm using Spring Boot 2.x, Spring Data REST, Spring HATEOAS.

I'm exposing a Repository over REST:

@Transactional
@PreAuthorize("isAuthenticated()")
public interface DocumentRepository extends JpaRepository<Document, Long>, JpaSpecificationExecutor {

 @Transactional(readOnly = true)
    @Query("SELECT d FROM Document d LEFT JOIN FETCH Contact c ON d.contact.id=c.id WHERE d.type='SALES_ORDER' AND d.status='PENDING' AND d.store.id=:storeId AND (SELECT COUNT(row) FROM DocumentRow row JOIN ProductAvailability pa ON (pa.productCode=row.productCode AND pa.productType=row.productType) WHERE row.document.id=d.id AND row.product.id IS NOT NULL AND pa.qtyOnHand-pa.qtyAllocated < row.qty)=0")
    Page<Document> findOrdersReadyToProcess(@Param("storeId") Long storeId, Pageable pageable);

Because I need some information of the Contact, I'm using a Spring Projection defined as:

@org.springframework.data.rest.core.config.Projection(name = "table", types = {Document.class})
public interface Projection {


    Instant getCreatedDate();

    Instant getDate();

    boolean isElectronic();

    BigDecimal getTotalAmount();

    DocumentType getType();

    String getCode();

    String getFiscalReceiptNumber();

    String getPurchaseRefCode();

    DocumentStatus getStatus();

    DocumentDeliveryStatus getDeliveryStatus();

    boolean isAssemble();

    ContactProjection getContact();

    StoreProjection getStore();

}

and the ContactProjection is:

@Projection(types = {Contact.class})
public interface ContactProjection {
    String getFullName();
}

Contact is defines as follows in Document entity:

@JsonDeserialize(using = ContactUriDeserializer.class)
@ManyToOne(fetch = FetchType.LAZY)
private Contact contact;

However, when I call the endpoint Hibernate do N subquery to fetch Contact information:

Hibernate: select document0_.`id` as id1_12_, document0_.`createdBy` as createdB2_12_, document0_.`createdDate` as createdD3_12_, document0_.`lastModifiedBy` as lastModi4_12_, document0_.`lastModifiedDate` as lastModi5_12_, document0_.`sid` as sid6_12_, document0_.`version` as version7_12_, document0_.`amount` as amount8_12_, document0_.`assemble` as assemble9_12_, document0_.`code` as code10_12_, document0_.`contact_id` as contact36_12_, document0_.`costAmount` as costAmo11_12_, document0_.`date` as date12_12_, document0_.`deliveryDate` as deliver13_12_, document0_.`deliveryStatus` as deliver14_12_, document0_.`destinationStore_id` as destina37_12_, document0_.`electronic` as electro15_12_, document0_.`embeddedContact` as embedde16_12_, document0_.`eyeExam_id` as eyeExam38_12_, document0_.`fiscalReceiptDate` as fiscalR17_12_, document0_.`fiscalReceiptNumber` as fiscalR18_12_, document0_.`fiscalReceiptPrintDate` as fiscalR19_12_, document0_.`fiscalReceiptSerialNumber` as fiscalR20_12_, document0_.`grossMargin` as grossMa21_12_, document0_.`grossProfit` as grossPr22_12_, document0_.`number` as number23_12_, document0_.`numberSeries` as numberS24_12_, document0_.`paymentTerm_id` as payment39_12_, document0_.`paymentType` as payment25_12_, document0_.`project_id` as project40_12_, document0_.`purchaseRefCode` as purchas26_12_, document0_.`rounding` as roundin27_12_, document0_.`sourceStore_id` as sourceS41_12_, document0_.`status` as status28_12_, document0_.`store_id` as store_i42_12_, document0_.`stsTaxDeductionDenial` as stsTaxD29_12_, document0_.`supplyType` as supplyT30_12_, document0_.`taxAmount` as taxAmou31_12_, document0_.`totalAmount` as totalAm32_12_, document0_.`type` as type33_12_, document0_.`workingDistance` as working34_12_, document0_.`year` as year35_12_ from `Document` document0_ left outer join `Contact` contact1_ on (document0_.`contact_id`=contact1_.`id`) where document0_.`type`='SALES_ORDER' and document0_.`status`='PENDING' and document0_.`store_id`=? and (select count(documentro2_.`id`) from `DocumentRow` documentro2_ inner join `ProductAvailability` productava3_ on (productava3_.`productCode`=documentro2_.`productCode` and productava3_.`productType`=documentro2_.`productType`) where documentro2_.`document_id`=document0_.`id` and (documentro2_.`product_id` is not null) and productava3_.`qtyOnHand`-productava3_.`qtyAllocated`<documentro2_.`qty`)=0 limit ?
Hibernate: select contact0_.`id` as id1_6_0_, contact0_.`createdBy` as createdB2_6_0_, contact0_.`createdDate` as createdD3_6_0_, contact0_.`lastModifiedBy` as lastModi4_6_0_, contact0_.`lastModifiedDate` as lastModi5_6_0_, contact0_.`sid` as sid6_6_0_, contact0_.`version` as version7_6_0_, contact0_.`billingAddress` as billingA8_6_0_, contact0_.`billingCity` as billingC9_6_0_, contact0_.`billingCountry` as billing10_6_0_, contact0_.`billingDistrict` as billing11_6_0_, contact0_.`billingZipCode` as billing12_6_0_, contact0_.`birthCity` as birthCi13_6_0_, contact0_.`birthDate` as birthDa14_6_0_, contact0_.`certifiedEmail` as certifi15_6_0_, contact0_.`companyName` as company16_6_0_, contact0_.`email` as email17_6_0_, contact0_.`fax` as fax18_6_0_, contact0_.`firstName` as firstNa19_6_0_, contact0_.`fullName` as fullNam20_6_0_, contact0_.`gender` as gender21_6_0_, contact0_.`iban` as iban22_6_0_, contact0_.`job` as job23_6_0_, contact0_.`landlinePhone` as landlin24_6_0_, contact0_.`lastName` as lastNam25_6_0_, contact0_.`mobilePhone` as mobileP26_6_0_, contact0_.`personType` as personT27_6_0_, contact0_.`preset` as preset28_6_0_, contact0_.`publicAdministration` as publicA29_6_0_, contact0_.`sdiAccountId` as sdiAcco30_6_0_, contact0_.`shippingAddress` as shippin31_6_0_, contact0_.`shippingCity` as shippin32_6_0_, contact0_.`shippingCountry` as shippin33_6_0_, contact0_.`shippingDistrict` as shippin34_6_0_, contact0_.`shippingZipCode` as shippin35_6_0_, contact0_.`store_id` as store_i45_6_0_, contact0_.`computerUser` as compute36_6_0_, contact0_.`drivingFrequency` as driving37_6_0_, contact0_.`electronicDeviceUser` as electro38_6_0_, contact0_.`jobCategory` as jobCate39_6_0_, contact0_.`sporty` as sporty40_6_0_, contact0_.`swift` as swift41_6_0_, contact0_.`taxCode` as taxCode42_6_0_, contact0_.`type` as type43_6_0_, contact0_.`vatNumber` as vatNumb44_6_0_ from `Contact` contact0_ where contact0_.`id`=?
Hibernate: select store0_.`id` as id1_40_0_, store0_.`createdBy` as createdB2_40_0_, store0_.`createdDate` as createdD3_40_0_, store0_.`lastModifiedBy` as lastModi4_40_0_, store0_.`lastModifiedDate` as lastModi5_40_0_, store0_.`sid` as sid6_40_0_, store0_.`version` as version7_40_0_, store0_.`address` as address8_40_0_, store0_.`certifiedEmail` as certifie9_40_0_, store0_.`city` as city10_40_0_, store0_.`code` as code11_40_0_, store0_.`country` as country12_40_0_, store0_.`district` as distric13_40_0_, store0_.`email` as email14_40_0_, store0_.`fax` as fax15_40_0_, store0_.`landlinePhone` as landlin16_40_0_, store0_.`mobilePhone` as mobileP17_40_0_, store0_.`name` as name18_40_0_, store0_.`zipCode` as zipCode19_40_0_ from `Store` store0_ where store0_.`id`=?
Hibernate: select contact0_.`id` as id1_6_0_, contact0_.`createdBy` as createdB2_6_0_, contact0_.`createdDate` as createdD3_6_0_, contact0_.`lastModifiedBy` as lastModi4_6_0_, contact0_.`lastModifiedDate` as lastModi5_6_0_, contact0_.`sid` as sid6_6_0_, contact0_.`version` as version7_6_0_, contact0_.`billingAddress` as billingA8_6_0_, contact0_.`billingCity` as billingC9_6_0_, contact0_.`billingCountry` as billing10_6_0_, contact0_.`billingDistrict` as billing11_6_0_, contact0_.`billingZipCode` as billing12_6_0_, contact0_.`birthCity` as birthCi13_6_0_, contact0_.`birthDate` as birthDa14_6_0_, contact0_.`certifiedEmail` as certifi15_6_0_, contact0_.`companyName` as company16_6_0_, contact0_.`email` as email17_6_0_, contact0_.`fax` as fax18_6_0_, contact0_.`firstName` as firstNa19_6_0_, contact0_.`fullName` as fullNam20_6_0_, contact0_.`gender` as gender21_6_0_, contact0_.`iban` as iban22_6_0_, contact0_.`job` as job23_6_0_, contact0_.`landlinePhone` as landlin24_6_0_, contact0_.`lastName` as lastNam25_6_0_, contact0_.`mobilePhone` as mobileP26_6_0_, contact0_.`personType` as personT27_6_0_, contact0_.`preset` as preset28_6_0_, contact0_.`publicAdministration` as publicA29_6_0_, contact0_.`sdiAccountId` as sdiAcco30_6_0_, contact0_.`shippingAddress` as shippin31_6_0_, contact0_.`shippingCity` as shippin32_6_0_, contact0_.`shippingCountry` as shippin33_6_0_, contact0_.`shippingDistrict` as shippin34_6_0_, contact0_.`shippingZipCode` as shippin35_6_0_, contact0_.`store_id` as store_i45_6_0_, contact0_.`computerUser` as compute36_6_0_, contact0_.`drivingFrequency` as driving37_6_0_, contact0_.`electronicDeviceUser` as electro38_6_0_, contact0_.`jobCategory` as jobCate39_6_0_, contact0_.`sporty` as sporty40_6_0_, contact0_.`swift` as swift41_6_0_, contact0_.`taxCode` as taxCode42_6_0_, contact0_.`type` as type43_6_0_, contact0_.`vatNumber` as vatNumb44_6_0_ from `Contact` contact0_ where contact0_.`id`=?
Hibernate: select contact0_.`id` as id1_6_0_, contact0_.`createdBy` as createdB2_6_0_, contact0_.`createdDate` as createdD3_6_0_, contact0_.`lastModifiedBy` as lastModi4_6_0_, contact0_.`lastModifiedDate` as lastModi5_6_0_, contact0_.`sid` as sid6_6_0_, contact0_.`version` as version7_6_0_, contact0_.`billingAddress` as billingA8_6_0_, contact0_.`billingCity` as billingC9_6_0_, contact0_.`billingCountry` as billing10_6_0_, contact0_.`billingDistrict` as billing11_6_0_, contact0_.`billingZipCode` as billing12_6_0_, contact0_.`birthCity` as birthCi13_6_0_, contact0_.`birthDate` as birthDa14_6_0_, contact0_.`certifiedEmail` as certifi15_6_0_, contact0_.`companyName` as company16_6_0_, contact0_.`email` as email17_6_0_, contact0_.`fax` as fax18_6_0_, contact0_.`firstName` as firstNa19_6_0_, contact0_.`fullName` as fullNam20_6_0_, contact0_.`gender` as gender21_6_0_, contact0_.`iban` as iban22_6_0_, contact0_.`job` as job23_6_0_, contact0_.`landlinePhone` as landlin24_6_0_, contact0_.`lastName` as lastNam25_6_0_, contact0_.`mobilePhone` as mobileP26_6_0_, contact0_.`personType` as personT27_6_0_, contact0_.`preset` as preset28_6_0_, contact0_.`publicAdministration` as publicA29_6_0_, contact0_.`sdiAccountId` as sdiAcco30_6_0_, contact0_.`shippingAddress` as shippin31_6_0_, contact0_.`shippingCity` as shippin32_6_0_, contact0_.`shippingCountry` as shippin33_6_0_, contact0_.`shippingDistrict` as shippin34_6_0_, contact0_.`shippingZipCode` as shippin35_6_0_, contact0_.`store_id` as store_i45_6_0_, contact0_.`computerUser` as compute36_6_0_, contact0_.`drivingFrequency` as driving37_6_0_, contact0_.`electronicDeviceUser` as electro38_6_0_, contact0_.`jobCategory` as jobCate39_6_0_, contact0_.`sporty` as sporty40_6_0_, contact0_.`swift` as swift41_6_0_, contact0_.`taxCode` as taxCode42_6_0_, contact0_.`type` as type43_6_0_, contact0_.`vatNumber` as vatNumb44_6_0_ from `Contact` contact0_ where contact0_.`id`=?
Hibernate: select contact0_.`id` as id1_6_0_, contact0_.`createdBy` as createdB2_6_0_, contact0_.`createdDate` as createdD3_6_0_, contact0_.`lastModifiedBy` as lastModi4_6_0_, contact0_.`lastModifiedDate` as lastModi5_6_0_, contact0_.`sid` as sid6_6_0_, contact0_.`version` as version7_6_0_, contact0_.`billingAddress` as billingA8_6_0_, contact0_.`billingCity` as billingC9_6_0_, contact0_.`billingCountry` as billing10_6_0_, contact0_.`billingDistrict` as billing11_6_0_, contact0_.`billingZipCode` as billing12_6_0_, contact0_.`birthCity` as birthCi13_6_0_, contact0_.`birthDate` as birthDa14_6_0_, contact0_.`certifiedEmail` as certifi15_6_0_, contact0_.`companyName` as company16_6_0_, contact0_.`email` as email17_6_0_, contact0_.`fax` as fax18_6_0_, contact0_.`firstName` as firstNa19_6_0_, contact0_.`fullName` as fullNam20_6_0_, contact0_.`gender` as gender21_6_0_, contact0_.`iban` as iban22_6_0_, contact0_.`job` as job23_6_0_, contact0_.`landlinePhone` as landlin24_6_0_, contact0_.`lastName` as lastNam25_6_0_, contact0_.`mobilePhone` as mobileP26_6_0_, contact0_.`personType` as personT27_6_0_, contact0_.`preset` as preset28_6_0_, contact0_.`publicAdministration` as publicA29_6_0_, contact0_.`sdiAccountId` as sdiAcco30_6_0_, contact0_.`shippingAddress` as shippin31_6_0_, contact0_.`shippingCity` as shippin32_6_0_, contact0_.`shippingCountry` as shippin33_6_0_, contact0_.`shippingDistrict` as shippin34_6_0_, contact0_.`shippingZipCode` as shippin35_6_0_, contact0_.`store_id` as store_i45_6_0_, contact0_.`computerUser` as compute36_6_0_, contact0_.`drivingFrequency` as driving37_6_0_, contact0_.`electronicDeviceUser` as electro38_6_0_, contact0_.`jobCategory` as jobCate39_6_0_, contact0_.`sporty` as sporty40_6_0_, contact0_.`swift` as swift41_6_0_, contact0_.`taxCode` as taxCode42_6_0_, contact0_.`type` as type43_6_0_, contact0_.`vatNumber` as vatNumb44_6_0_ from `Contact` contact0_ where contact0_.`id`=?

I want to avoid this N+1 SELECT problem! I tried several modification to the query, but every time I ended up to have these subqueries.

Am I doing something wrong? Why Hibernate doesn't fetch contact property and avoid subsequent subqueries?

Any hint would be appreciated.

drenda
  • 5,846
  • 11
  • 68
  • 141
  • @AlexSalauyou I made a try but subqueryes are always made. – drenda Aug 15 '19 at 09:02
  • By default, to fetch eager many-to-one, subsequent selects are used. This is done because the entity may already exist in a context, so fetching is not needed. To override, use `@Fetch(FetchMode.JOIN)`. – Alex Salauyou Aug 15 '19 at 09:02
  • because you made association lazy – Alex Salauyou Aug 15 '19 at 09:03
  • @AlexSalauyou I tried with association EAGER but subqueries are executed as well. Furthemore I would prefer avoiding to make always a JOIN. I need that JOIN just into 1 query. I would like to fetch contact just in that particular query and to avoid unecessary JOINs in the rest of the application. – drenda Aug 15 '19 at 09:08
  • Can you check the behaviour by diabling the Projection. – Shailendra Aug 15 '19 at 09:19
  • @Shailendra Disabling projection no more subqueries are made, but that's because in the REST response the contact is not sent but just a link reference to it. – drenda Aug 16 '19 at 07:08
  • 1
    That means it is the projection part which is actually making it issue the call. Hibernate is correctly issuing the SQL without the subquery. – Shailendra Aug 16 '19 at 08:53
  • I think you are right. That's quite annoying though. I wish there is a way to avoid this and permit the Spring Projection to get the data from the Hibernate JOIN without additional queries. – drenda Aug 16 '19 at 09:05

1 Answers1

0

Try to add the following annotation to your repository method:

@EntityGraph("contact")
Selindek
  • 3,269
  • 1
  • 18
  • 25