I've just encountered the following MySQL error in my web app
Too many tables; MySQL can only use 61 tables in a join
This is occurring when performing a Hibernate Search (version 5.5.2) query and I'm not entirely sure why that many joins are required. Here is a simplified example of my entity model:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public class Profile {
Integer id;
#ManyToOne
RelatedEntityOne joinOne;
}
#Indexed
#Entity
public class BusinessProfile extends Profile {
#ManyToOne
RelatedEntityTwo joinTwo;
}
#Indexed
#Entity
public class UserProfile extends Profile {
#ManyToOne
RelatedEntityThree joinThree;
}
Here is the code that performs the query
FullTextEntityManager ftem = Search.getFullTextEntityManager(em);
FullTextQuery fullTextQuery = ftem.createFullTextQuery(myQuery, UserProfile.class);
List result = fullTextQuery.getResultList();
And here is an example of the generated SQL
SELECT *
FROM Profile root
LEFT OUTER JOIN BusinessProfile join_1 ON root.id = join_1.id
LEFT OUTER JOIN UserProfile join_2 ON root.id = join_2.id
LEFT OUTER JOIN RelatedEntityOne join_3 ON root.x = join_3.x
LEFT OUTER JOIN RelatedEntityTwo join_4 ON join_1.x = join_4.x
LEFT OUTER JOIN RelatedEntityThree join_5 ON join_2.x = join_5.x
WHERE root.id IN (...)
So in this simplified example there are 5 joins. Which would make sense if I was performing a query on the parent class Profile. However I've passed the child class UserProfile to the createFullTextQuery method, so I would expect the generated SQL to look more like this:
SELECT *
FROM UserProfile root
LEFT OUTER JOIN Profile join_1 ON root.id = join_1.id
LEFT OUTER JOIN RelatedEntityOne join_2 ON join_1.x = join_2.x
LEFT OUTER JOIN RelatedEntityThree join_3 ON root.x = join_3.x
WHERE root.id IN (...)
I'm not sure if this is an issue with Hibernate, Hibernate Search, my own code, or if there is no issue and everything is behaving as intended. I don't see any reason for it to be joining the sibling tables given that we've identified which child table to use.
I confirm it's a bug in Hibernate Search. Here is the JIRA I just created: https://hibernate.atlassian.net/browse/HSEARCH-2301 .
I already wrote a fix but I have to go and doesn't have the time to clean it up; I'll post a PR later today.
Thanks for spotting this issue!
UPDATE we fixed it in 5.5.4.Final: http://in.relation.to/2016/06/29/Polishing-Polishing-And-More-Polishing-Hibernate-Search-5-5-4-Final/
Related
I have written a join query to avoid n+1 problem. It is successfully working when I checked with integration test case. But when it comes to the same query executes while application running in server, the same n+1 problem is coming. I have removed the the line join query call and tested by deploying, then no n+1 problem is coming. The query generated at integration test case and while running server both are same. But I am unable to understand why n+1 is happening when checked with deployed application. Configurations for both application and test case are same.
Query executing is below, Query id very huge so I posted the joins and tables involved in query, The below query executing for both test case and application level.
from ITINERARY_TRAVELERS itineraryt0_ left outer join ITINERARIES itinerary1_ on itineraryt0_.ITINERARY_ID=itinerary1_.ID left outer join COUPON_HISTORY couponhist3_ on itinerary1_.ID=couponhist3_.ITINERARY_ID left outer join REWARD_USAGE_DATA rewardusag4_ on itinerary1_.ID=rewardusag4_.ITINERARY_ID left outer join CUSTOMER_REQUEST_INFO customertr5_ on itinerary1_.CUSTOMER_REQUEST_ID=customertr5_.ID left outer join ITINERARY_CANCELLATION_DETAILS itineraryc6_ on itinerary1_.ID=itineraryc6_.ITINERARY_ID left outer join INSURANCE_DETAILS insuranced7_ on itinerary1_.ID=insuranced7_.ITINERARY_ID left outer join EMP_EMPLOYER_DETAILS employeeem8_ on itinerary1_.ID=employeeem8_.ITINERARY_ID left outer join CUSTOMERS customer9_ on itinerary1_.CUSTOMER_ID=customer9_.ID left outer join REWARD_PROGRAM rewardprog10_ on customer9_.ID=rewardprog10_.CUSTOMER_ID left outer join CUSTOMER_CONTACT_DETAILS customerco11_ on customer9_.ID=customerco11_.CUSTOMER_ID left outer join CUSTOMER_PREFERRED_AIRLINES customerpr12_ on customer9_.ID=customerpr12_.CUSTOMER_ID left outer join TRAVELERS traveler2_ on itineraryt0_.TRAVELERS_ID=traveler2_.ID where itineraryt0_.TRAVELERS_ID=3183
The extra queries which are executing while checking when application deployed is in below format, with different ids (N+1)
from ITINERARY_TRAVELERS itneraries0_ left outer join TRAVELERS traveler1_ on itneraries0_.TRAVELERS_ID=traveler1_.ID left outer join CUSTOMERS customer2_ on traveler1_.CUSTOMER_ID=customer2_.ID where itneraries0_.ITINERARY_ID=697049
Entity classes are as follows
#Table(name = "TRAVELERS")
#Cacheable(true)
#Audited
public class Traveler{
......
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "traveler")
#OptimisticLock(excluded = true)
public Set<ItineraryTravelerAssosciation> getItnerariesTravlersAssosciation() {
return itnerariesTravlersAssosciation;
}
#Table(name = "ITINERARY_TRAVELERS")
#Cacheable(true)
#Audited
public class ItineraryTravelerAssosciation extends BaseDto {
private static final long serialVersionUID = 10124L;
private String passengerType;
private Itinerary itinerary;
private Traveler traveler;
......
#ManyToOne
#OptimisticLock(excluded = true)
#JoinColumn(name = "ITINERARY_ID")
public Itinerary getItinerary() {
return itinerary;
}
#ManyToOne
#OptimisticLock(excluded = true)
#JoinColumn(name = "TRAVELERS_ID")
public Traveler getTraveler() {
return traveler;
}
In Application I am reading First traveler object with id, using entity manger find with Id. On that when I call getItnerariesTravlersAssosciation(), N+1 queries(query 2) are executing.
But this behavior is not re producing when I run integration test case. The N+1 calls happening only when application is deployed.
We have a Spring Boot / JPA application that has an entity which specifies a conversion from and to different units of measure. This conversion is specific for each product we work with and therefore this entity has a composite key with 3 attributes: FromUnitOfMeasure, ToUnitOfMeasure and Product.
#Getter
#Setter
#EqualsAndHashCode(of = "unitOfMeasureConversionByMaterialCompositeKey")
#NoArgsConstructor
#RequiredArgsConstructor
#Entity
public class UnitOfMeasureConversionByMaterial {
#EmbeddedId
#NonNull
private UnitOfMeasureConversionByMaterialCompositeKey unitOfMeasureConversionByMaterialCompositeKey;
#Data
#NoArgsConstructor
#AllArgsConstructor
#Embeddable
#EqualsAndHashCode
public static class UnitOfMeasureConversionByMaterialCompositeKey implements Serializable {
#ManyToOne(fetch = FetchType.LAZY)
#NonNull
private Product product;
#ManyToOne(fetch = FetchType.LAZY)
#NonNull
private UnitOfMeasure fromUnitOfMeasure;
#ManyToOne(fetch = FetchType.LAZY)
#NonNull
private UnitOfMeasure toUnitOfMeasure;
}
private Float conversionValue;
UnitOfMeasure class:
#Getter
#Setter
#ToString(of="id")
#EqualsAndHashCode(of = "id")
#AllArgsConstructor
#NoArgsConstructor
#Entity
public class UnitOfMeasure implements Serializable {
#Id
private String id;
private String description;
public UnitOfMeasure(String id) {
this.id = id;
}
}
The entire logic runs on an async thread, so no open session is available (equivalent to spring.jpa.open-in-view=false).
The #ManyToOne fields are Lazy, so we employ a JOIN FETCH query in our unit conversion repository to get both the conversions and #ManyToOne fields (thus following the best practice described at https://vladmihalcea.com/the-best-way-to-handle-the-lazyinitializationexception/)
#Query("SELECT cup FROM UnitOfMeasureConversionByMaterial cbm "
+ "LEFT JOIN FETCH cbm.unitOfMeasureConversionByMaterialCompositeKey.product p "
+ "LEFT JOIN FETCH cbm.unitOfMeasureConversionByMaterialCompositeKey.fromUnitOfMeasure fum "
+ "LEFT JOIN FETCH cbm.unitOfMeasureConversionByMaterialCompositeKey.toUnitOfMeasure tum")
List<UnitOfMeasureConversionByMaterial> customFindAllJoinProductsAndUom();
The application then proceeds to iterate all conversion entities and populate a map which indexes conversions by product, fromUnitOfMeasure and targetUnitOfMeasure:
protected Map<Product,Map<UnitOfMeasure,Map<UnitOfMeasure,Float>>> indexedConversionMap = new HashMap<>();
Here is the part of the code where I insert values in the map.
The code runs successfully for the first 24.500 conversions, but then throws a 'org.hibernate.LazyInitializationException: could not initialize proxy [test.domain.UnitOfMeasure#kg] - no Session' exception when populating the final level of the map (putting the float conversion value for a given ToUnitOfMeasure).
for (UnitOfMeasureConversionByMaterial unitOfMeasureConversionByMaterial : conversionsExtractedWithJoinFetchQuery) {
Product product = unitOfMeasureConversionByMaterial.getProduct();
UnitOfMeasure fromUnitOfMeasure = unitOfMeasureConversionByMaterial.getFromUnitOfMeasure ();
UnitOfMeasure toUnitOfMeasure = unitOfMeasureConversionByMaterial.getToUnitOfMeasure ();
Float conversion = unitOfMeasureConversionByMaterial.getConversionValue();
if (!unitOfMeasureProjection.indexedConversionMap.containsKey(product)) {
unitOfMeasureProjection.indexedConversionMap.put(product, new HashMap<>());
}
if (!unitOfMeasureProjection.indexedConversionMap.get(product).containsKey(fromUnitOfMeasure)) {
unitOfMeasureProjection.indexedConversionMap.get(product).put(fromUnitOfMeasure, new HashMap<>());
}
unitOfMeasureProjection.indexedConversionMap.get(product).get(fromUnitOfMeasure).put(
toUnitOfMeasure, conversion); // ERROR happens here
}
The error message is:
org.hibernate.LazyInitializationException: could not initialize proxy [test.domain.UnitOfMeasure#kg] - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:175)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:315)
at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:45)
at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95)
at test.domain.unitofmeasureconversionbymaterial.UnitOfMeasure$HibernateProxy$2LNVM7uD.hashCode(Unknown Source)
at java.base/java.util.HashMap.hash(HashMap.java:338)
at java.base/java.util.HashMap.put(HashMap.java:610)
at test.UnitOfMeasureProjectionFactory.getUnitOfMeasureProjectionWithConversions(UnitOfMeasureProjectionFactory.java:104)
at test.ConversionService.executeConversion(ConversionService.java:326)
at test.executeConversion$$FastClassBySpringCGLIB$$a7815e11.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)
at test.ConversionService$$EnhancerBySpringCGLIB$$77fac61.executeConversion(<generated>)
at test.job.executeAsJob(ConversionJob.java:36)
at test.job.executeAsJob(ConversionJob.java:20)
at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at java.base/java.lang.Thread.run(Thread.java:833)
Some additional information :
I have have tried to delombok Equals and HashCode at the UnitOfMeasure entity
They work normally and only depend on the String id attribute
spring.jpa.open-in-view=true is not an option as we are starting a different thread. Performance is also key on this application so we must not count on lazy fetching
An overarching #Transactional annotation in our service is also not an option as hundreds of thousands of records are being saved in batches. We have memory constraints (application and database) which would be violated by a single-transaction operation
What I do not understand is:
Why does the code run with no issues for the first 24.500 conversions and only then the error shows up
Why even when we extract conversions with JOIN FETCH for all #ManyToOne attributes (which in turn have simple String ids) does Hibernate try to access the database to fetch the information
What could be going wrong? Any ideas as to how to debug this exception?
Edit 1:
Here's the SQL for the custom Query:
select
unitofmeas0_.product_id as product_4_31_0_,
unitofmeas0_.to_unit_of_measure_id as unit_of_3_31_0_,
unitofmeas0_.from_unit_of_measure_id as unit_of_2_31_0_,
product1_.id as id1_97_1_,
unitofmeas2_.id as id1_129_2_,
unitofmeas3_.id as id1_129_3_,
unitofmeas0_.conversion_value as conversi1_31_0_,
product1_.active as active2_97_1_,
product1_.descontinuation_date as desconti3_97_1_,
product1_.introduction_date as introduc4_97_1_,
product1_.description as descript5_97_1_,
product1_.ean as ean6_97_1_,
product1_.operational_model as operatio7_97_1_,
product1_.ncm as ncm8_97_1_,
product1_.standard_unit_of_measure_id as standard9_97_1_,
product1_.transfer_unit_of_measure_id as transfer10_97_1_,
product1_.sales_unit_of_measure_id as sales_un11_97_1_,
unitofmeas2_.description as descript2_129_2_,
unitofmeas3_.description as descript2_129_3_
from
unit_of_measure_conversion_by_material unitofmeas0_
left outer join
product product1_
on unitofmeas0_.product_id=product_.id
left outer join
unit_of_measure unitofmeas2_
on unitofmeas0_.from_unit_of_measure_id=unitofmeas2_.id
left outer join
unit_of_measure unitofmeas3_
on unitofmeas0_.to_unit_of_measure_id=unitofmeas3_.id
We have other relationships to UnitOfMeasure in the Product entity, but we believe that is not the problem since we tried to JOIN FETCH the Product fields with the following Query and the error persists.
#Query("SELECT cup FROM UnitOfMeasureConversionByMaterial cbm "
+ "LEFT JOIN FETCH cbm.unitOfMeasureConversionByMaterialCompositeKey.product p "
+ "LEFT JOIN FETCH cbm.unitOfMeasureConversionByMaterialCompositeKey.fromUnitOfMeasure fum "
+ "LEFT JOIN FETCH cbm.unitOfMeasureConversionByMaterialCompositeKey.toUnitOfMeasure tum "
+ "LEFT JOIN FETCH p.standardUnitOfMeasure stum "
+ "LEFT JOIN FETCH p.salesUnitOfMeasure saum "
+ "LEFT JOIN FETCH p.transferUnitOfMeasure tum")
List<UnitOfMeasureConversionByMaterial> customFindAllJoinProductsAndUom();
The SQL:
select
unitofmeas0_.product_id as product_4_31_0_,
unitofmeas0_.to_unit_of_measure_id as unit_of_3_31_0_,
unitofmeas0_.from_unit_of_measure_id as unit_of_2_31_0_,
product1_.id as id1_97_1_,
unitofmeas2_.id as id1_129_2_,
unitofmeas3_.id as id1_129_3_,
unitofmeas4_.id as id1_129_4_,
unitofmeas5_.id as id1_129_5_,
unitofmeas6_.id as id1_129_6_,
unitofmeas0_.conversion_value as conversi1_31_0_,
product1_.active as active2_97_1_,
product1_.descontinuation_date as desconti3_97_1_,
product1_.introduction_date as introduc4_97_1_,
product1_.description as descript5_97_1_,
product1_.ean as ean6_97_1_,
product1_.operational_model as operatio7_97_1_,
product1_.ncm as ncm8_97_1_,
product1_.standard_unit_of_measure_id as standard9_97_1_,
product1_.transfer_unit_of_measure_id as transfer10_97_1_,
product1_.sales_unit_of_measure_id as sales_un11_97_1_,
unitofmeas2_.description as descript2_129_2_,
unitofmeas3_.description as descript2_129_3_,
unitofmeas4_.description as descript2_129_4_,
unitofmeas5_.description as descript2_129_5_,
unitofmeas6_.description as descript2_129_6_
from
unit_of_measure_conversion_by_material unitofmeas0_
left outer join
product product1_
on unitofmeas0_.product_id=product1_.id
left outer join
unit_of_measure unitofmeas2_
on unitofmeas0_.from_unit_of_measure_id=unitofmeas2_.id
left outer join
unit_of_measure unitofmeas3_
on unitofmeas0_.to_unit_of_measure_id=unitofmeas3_.id
left outer join
unit_of_measure unitofmeas4_
on product1_.standard_unit_of_measure_id=unitofmeas4_.id
left outer join
unit_of_measure unitofmeas5_
on product1_.sales_unit_of_measure_id=unitofmeas5_.id
left outer join
unit_of_measure unitofmeas6_
on product1_.transfer_unit_of_measure_id_id=unitofmeas6_.id
Edit 2:
We also tried to separete the method calls in the following way to analyze the problem:
for (UnitOfMeasureConversionByMaterial unitOfMeasureConversionByMaterial : conversionsExtractedWithJoinFetchQuery) {
unitOfMeasureConversionByMaterial.getFromUnitOfMeasure().getId();
unitOfMeasureConversionByMaterial.getFromUnitOfMeasure().hashCode();
unitOfMeasureConversionByMaterial.getToUnitOfMeasure().getId();
unitOfMeasureConversionByMaterial.getToUnitOfMeasure().hashCode();
}
Everything works the intended way until we try to call the "hashCode()" method in "toUnitOfMeasure" in the fifth line. The "unitOfMeasureConversionByMaterial.getToUnitOfMeasure()" part works correctly, but as soon as we try to call the "hashCode()" method the code generates the LazyInitializationException. It doesn't even execute the first line of the method.
Why does the code run with no issues for the first 24.500 conversions and only then the error shows up
HashMap uses not only hashCode() method but equals() method too. equals() is used to get a value from a map, when a bucket has more than one value. I didn't look into actual implementation of HashMap, it can use equals(), for example, during increasing memory of the conatiner.
Also, for example, toString() method of persistent object can be used somewhere. For example during logging of an error.
Keep in mind, that Hibernate doesn't look into methods of persistent class. So if any method like toString() is invoked, Hibernate tries to load all lazy associations, because they can be used in the method.
How to debug
Temporary open Hibernate session for all part of the code.
Enable SQL logging
Try to set breakpoint after 24500 conversions and debug the code
Keep in mind that debugger can invoke toString() method and cause to load lazy associations.
Some notices
Overriding hashCode() and equals() methods of persistent classes is a bad idea. Using Lombok for that is a very very bad idea.
Using persistent classes as keys of a map is a bas idea too.
Using composite keys is not convenient almost in all cases.
I have written code that has N+1 problem.
It is a BI-directional ManyToMany relationship.
I'm attempting to solve it using LEFT JOIN FETCH
The issue i am having is that when i look at the results, only 1 single record from the right side is being joined. There should be many more for each left record.
The left records come back correct, but the Set attribute only ever contains 1 instance from the right.
value = "SELECT cat from CatEntity cat "
+ "INNER JOIN cat.owner o "
+ "LEFT JOIN FETCH cat.toys "
+ "WHERE (o.name = :something)
I get all 69 cat records, but the cats that have toys only have 1 toy.
If i remove the LEFT JOIN FETCH. I get all cats and all the complete toys... and of course the N+1 problem.
When i add in LEFT JOIN FETCH.. it solves N+1 problem but doesnt return full toy set.
public class CatEntity implements Serializable {
#JoinTable(
name = "cat_toy",
joinColumns = {#JoinColumn(name = "cat_id")},
inverseJoinColumns = {#JoinColumn(name = "toy_id")})
#Fetch(FetchMode.JOIN)
#OrderBy(value = "toy_index desc")
private Set<ToyEntity> toys;
}
}
public class ToyEntity implements Serializable {
#ManyToMany(mappedBy = "toys")
Set<CatEntity> cats;
}
How do i avoid N+1 problem but fully populate the Set?
//Company frowns upon publishing code so i use cat/toy as example replacing class names.
I found the solution to my problem. I will post here hoping it helps others.
I was using
#Fetch(FetchMode.JOIN)
in my code. I needed to use
#Fetch(FetchMode.SUBSELECT)
This is specifically designed for collections. It populates the right side of a ManyToMany bidirection relationship using 2 queries rather than N+1.
It first gets all the cats. It then gets all the toys. Only 2 queries are executed.
You should NOT put LEFT JOIN FETCH in your query itself. Just add the fetch mode to the entity mapping.
hope this helps others.
I have Item.java entity with below properties
#OneToMany(cascade=CascadeType.ALL, mappedBy="kitItem", fetch=FetchType.EAGER)
private Set<KitItemDetails> kitItemDetails = new HashSet<KitItemDetails>(0);
.... Other properties with getter and setter
I have KitItemDetails.java entity with below properties
#ManyToOne(cascade=CascadeType.ALL)
private Item kitItem;
In my DAO access class we are using below query to fetch all items with other related attributes as EAGER
List<Item> items = getCurrentSession().createCriteria(Item.class).addOrder(Order.asc("itemOrder"))
.list();
But for this its firing n select queries to fecth item details as below
select kititemdet0_.kitItem_id as kitItem_2_9_0_, kititemdet0_.id as id1_12_0_, kititemdet0_.id as id1_12_1_, kititemdet0_.kitItem_id as kitItem_2_12_1_, kititemdet0_.ITEM_ID as ITEM_ID3_12_1_, item1_.id as id1_9_2_, item1_.BAR_CODE as BAR_CODE2_9_2_, item1_.COLOR_CODE as COLOR_CO3_9_2_, item1_.IS_ACTIVE as IS_ACTIV4_9_2_, item1_.ITEM_CATEGORY_ID as ITEM_CA14_9_2_, item1_.ITEM_CODE as ITEM_COD5_9_2_, item1_.ITEM_DISP_NAME as ITEM_DIS6_9_2_, item1_.ITEM_IMAGE as ITEM_IMA7_9_2_, item1_.ITEM_NAME as ITEM_NAM8_9_2_, item1_.ITEM_ORDER as ITEM_ORD9_9_2_, item1_.ITEM_PRICE as ITEM_PR10_9_2_, item1_.ITEM_PRICE_WITH_TAX as ITEM_PR11_9_2_, item1_.ITEM_SUB_CATEGORY_ID as ITEM_SU15_9_2_, item1_.ITEM_TYPE as ITEM_TY12_9_2_, item1_.TAX_ID as TAX_ID16_9_2_, item1_.TAX_CODE as TAX_COD13_9_2_, itemcatego2_.id as id1_10_3_, itemcatego2_.COLOR_CODE as COLOR_CO2_10_3_, itemcatego2_.IS_ACTIVE as IS_ACTIV3_10_3_, itemcatego2_.ITEM_CATEGORY_NAME as ITEM_CAT4_10_3_, itemcatego2_.LOCATION_ID as LOCATION6_10_3_, itemcatego2_.PRINTER_NAME as PRINTER_5_10_3_, itemsubcat3_.id as id1_11_4_, itemsubcat3_.COLOR_CODE as COLOR_CO2_11_4_, itemsubcat3_.IS_ACTIVE as IS_ACTIV3_11_4_, itemsubcat3_.ITEM_CATEGORY_ID as ITEM_CAT5_11_4_, itemsubcat3_.ITEM_SUB_CATEGORY_NAME as ITEM_SUB4_11_4_, tax4_.id as id1_21_5_, tax4_.IS_ACTIVE as IS_ACTIV2_21_5_, tax4_.LOCATION_ID as LOCATION6_21_5_, tax4_.TAX_CODE as TAX_CODE3_21_5_, tax4_.TAX_NAME as TAX_NAME4_21_5_, tax4_.TAX_PER as TAX_PER5_21_5_ from KIT_ITEM_DETAILS kititemdet0_ inner join ITEM item1_ on kititemdet0_.ITEM_ID=item1_.id left outer join ITEM_CATEGORY itemcatego2_ on item1_.ITEM_CATEGORY_ID=itemcatego2_.id left outer join ITEM_SUB_CATEGORY itemsubcat3_ on item1_.ITEM_SUB_CATEGORY_ID=itemsubcat3_.id left outer join TAX tax4_ on item1_.TAX_ID=tax4_.id where kititemdet0_.kitItem_id=?
I tried with mappedBy, JoinColumn, JoinType, FetchMode, etc but did not worked out, what could be the issue ?
In general, having a relation set as EAGER does result in the "N select queries" problem when using criteria queries. You can overcome this by explicitly instructing Hibernate to do a fetch join. See the Hibernate documentation for more details.
The documentation contains an example very similar to your scenario (tweaked to use your class names):
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Item> query = builder.createQuery( Item.class );
Root<Item> root = query.from( Item.class );
root.fetch( "kitItemDetails", JoinType.LEFT);
query.select(root).where(
builder.and(
builder.equal(root.get("foo"), foo),
builder.equal(root.get("bar"), bar)
)
);
Item item = entityManager.createQuery( query ).getSingleResult();
// Alternatively, .getResultList() to return List<Item>
#Fetch(FetchMode.JOIN) will not work in your case because, as the documentation goes on to state in its example for FetchMode.JOIN:
The reason why we are not using a JPQL query to fetch multiple Department entities is because the FetchMode.JOIN strategy would be overridden by the query fetching directive.
On my project I'm using Groovy with Spring Data JPA's Specification's to construct Hibernate queries.
I can't provide my actual queries but to illustrate my problem let's say I have Building entities, and each Building has Floors and each Floor has both Rooms and Windows.
The behavior I'm attempting to simulate is something like this native SQL query:
SELECT b.*, r.*
FROM building b
INNER JOIN floor f ON b.id = f.building_id
INNER JOIN window w ON f.id = w.floor_id
LEFT OUTER JOIN room r ON f.id = r.floor_id
WHERE w.id = 1;
I have a specification similar to the below:
public class MySpec implements Specification<Building> {
#Override
public Predicate toPredicate(final Root<Building> root, final CriteriaQuery<?> query, final CriteriaBuilder cb) {
final Join floorsJoin = root.join("floors");
final Join windowsJoin = floorsJoin.join("windows");
//I'd like to remove this line
final Fetch floorsFetch = root.fetch("floors"); // <---
floorsFetch.fetch("rooms", JoinType.LEFT);
cb.equal(windowsJoin.get("id"), 1L);
}
}
The line annotated above is my issue. If I leave it, the generated query looks something like this:
SELECT b.*, f2.*, r.*
FROM building b
INNER JOIN floor f ON b.id = f.building_id
INNER JOIN window w ON f.id = w.floor_id
INNER JOIN floor f2 ON b.id = f2.building_id
LEFT OUTER JOIN room r ON f2.id = r.floor_id
WHERE w.id = 1;
(notice the duplicate INNER JOIN of floor and the unneeded f2.* data)
If I remove it, and use the floorsJoin instead to fetch rooms, I get the following Hibernate error:
org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list
The unneeded f2.* data would be OK except I can't replace the above floorsJoin with the floorsFetch because I need to join with the windows table (without fetching windows) and the Fetch class doesn't have a .join method.
I'm having a difficult time figuring out how I would accomplish what I need while still generating a single query; surely I must be missing something simple.
Any thoughts or advice you could provide would be much appreciated.
Thanks a lot,
B.J.
Well it's not that simple with the JPA Criteria API. With Hibernate you could simply cast the Fetch to a Join I guess but that's not going to help you that much. I am not sure how you use the specification in this case, but if you could write the query as a whole it could look like the following
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = cb.createTupleQuery();
Root<Building> root = cq.from(Building.class);
final Join floorsJoin = root.join("floors");
final Join windowsJoin = floorsJoin.join("windows");
final Join roomsJoin = floorsJoin.join("rooms", JoinType.LEFT);
cb.equal(windowsJoin.get("id"), 1L);
cq.multiselect(
root,
roomsJoin
);