hibernate generate query without all outer join fetch possible - java

I have 5 entities :
#Entity
public class A {
#OneToMany(mappedBy="a", fetch=FetchType.EAGER)
#Fetch(FetchMode.JOIN)
private Set<B1> b1s;
#OneToMany(mappedBy="a", fetch=FetchType.EAGER)
#Fetch(FetchMode.JOIN)
private Set<B2> b2s;
}
#Entity
public class B1 {
#ManyToOne
#JoinColumn(name="c")
private C c;
}
#Entity
public class B2 {
#ManyToOne
#JoinColumn(name="c")
private C c;
}
#Entity
public class C {
#OneToMany(mappedBy="d", fetch=FetchType.EAGER)
#Fetch(FetchMode.JOIN)
private Set<D> ds;
}
#Entity
public classD {
}
So in short,
A eager join two set of Entity B1 and B2, each one eager joining (implicitely) an Entity C which in turn eager join a set of Entity D.
when loading an A object, the generated query look like
select (...) from A a0
left outer join B1 b1 on a0.id=b1.aid
left outer join C c1 on b1.cid=c1.id
left outer join D d1 on c1.id=d1.cid
left outer join B2 b2 on a0.id=b2.aid
left outer join C c2 on b2.cid=c2.id
where a0.id=?
The problem is that the Set of Entity D wich are linked to c2 (C Entity loaded via B2 Entity) are not loaded in the same query, resulting in N subsequent queries for each c2 object.
I would expect Hibernate to generate another left outer join for these objects in the first query, as it already does for the first occurence of D.
I am missing something ? I use hibernate 3.6 on an Oracle DB, is it an known issue ?
Thank you for your time.

Indeed, this seems to be a bug or an incomplete feature. I was able to reproduce it in a simpler scenario as well:
#Entity
public class A {
#OneToMany(fetch=FetchType.EAGER)
#Fetch(FetchMode.JOIN)
#JoinColumn
private Set<B> bs1;
#OneToMany(fetch=FetchType.EAGER)
#Fetch(FetchMode.JOIN)
#JoinColumn
private Set<B> bs2;
}
#Entity
public class B {
#OneToMany(fetch=FetchType.EAGER)
#Fetch(FetchMode.JOIN)
#JoinColumn
private Set<C> cs;
}
#Entity
public class C {
}
When loading A by id, the following joins are generated:
from
A a0_
left outer join
B bs1x1_
on a0_.id=bs1x1_.bs1_id
left outer join
C cs2_
on bs1x1_.id=cs2_.cs_id
left outer join
B bs2x3_
on a0_.id=bs2x3_.bs2_id
So, if you really want to fetch everything in one query, you'll have to do it with HQL:
select a from A a
left join fetch a.b1s b1
left join fetch b1.c c1
left join fetch c1.ds
left join fetch a.b2s b2
left join fetch b2.c c2
left join fetch c2.ds
where a.id = ?
However, I assume that the collections being joined are really small. Because these kinds of joins produce full Cartesian product and are considered a very bad anti-pattern.
Suppose that each of the collections contains only 100 rows (A has 100 b1s and 100 b2s, and each of b1s and b2s has a C which has 100 ds). Then you will get and read a result set containing 100 million rows!
But with lazy collections and batch fetching for example, you would read a total of about 400 rows in a few queries, which is much faster than reading 100 million rows in one query.

Related

Hibernate n+1 problem not coming in test case but coming in server

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.

JPA - Ignore join operation in namedQuery in EclipseLink

I have below scenario
Entity TableA :
#Entity
#Table(name = "TABLE_A")
#NamedQueries({
#NamedQuery(name = "TableA.namedQ1", query = "SELECT t1 FROM TableA t1 JOIN FETCH t2.TableB t2"
+ " WHERE <conditions here>"),
#NamedQuery(<Need query here which will ignore mapping below and return rows only for TableA>)
} )
public class TableA implements Serializable{
#Id
#Column(name = "id")
private int id
...
...
...
#OneToMany(mappedBy = "tableA", cascade = CascadeType.ALL ,fetch=FetchType.LAZY)
private List<TableB> tableB;
}
Entity TableB :
#Entity
#Table(name = "TABLE_B")
public class TableB implements Serializable{
#Id
#Column(name = "id1")
private int id1
...
...
...
#ManyToOne
#JoinColumn(name = "id",insertable = false, updatable = false)
private TableA tableA;
}
I am facing below two issues :
Query mentioned above i.e
SELECT t1 FROM TableA t1 JOIN FETCH t2.TableB t2
takes long time to execute. around 30 seconds. But the same query for same dataset takles hardly 3-4 seconds in SQL developer. ANythnig I should do in code to make it run faster?
I have requirement where i dont need data from other table(retrived via mapping). I would be needing data only from TableA. I tried below named query but it run separate query against TableB for each row in TableA which takes 4+ minutes to execute.
"SELECT t1 FROM TableA t1 where <condition goes here>"
What modifications I have to do in query to ignore mapping. I would need to retain annotations(#OneToMany) as I will need it in namedQ1.
Thanks in anticipation
When you use FETCH, you are asking to retrieve the data for the collection in
advance. If you don't need the elements from TableB then your query should be:
SELECT t1 FROM TableA t1 left join t1.tableB t2
Note that the join is only necessary if you need to add some condition to t2.
For example:
SELECT t1 FROM TableA t1 left join t1.tableB t2 WHERE t2.field = 123
if this is not your case, than this should be enough:
SELECT t1 FROM TableA t1;
In all these cases, it will create a proxy for the collection TableB
and won't need to access the table on the database, unless you need to use the collection later on.
For anyone having similar questions
Question 1 in OP : https://stackoverflow.com/questions/61573091/long-time-of-fetching-data-from-oracledb-using-eclipselink
Question 2 in OP: Solved using #Davide solution

How nested FETCH JOIN works on Hibernate using JpaRepository and #Query annotation?

I have the following problem (pseudo-java-code):
Let me a class A,B,C with the following relationships:
#Entity
#Table(name = "A")
public class A {
#OneToMany(mappedBy = "a")
private B b;
}
#Entity
#Table(name = "B")
public class B {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "a_id")
private A a;
#OneToOne(mappedBy = "b", fetch = FetchType.LAZY)
private C c;
}
#Entity
#Table(name = "C")
public class C {
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "b_id")
private B b;
}
I'm using JpaRepository with #Query annotation and I implemented the following query:
#Query("SELECT DISTINCT(a) FROM A a "
+ "LEFT JOIN FETCH a.b as b"
+ "WHERE a.id = :id ")
A findById(#Param("id") Integer id);
I want retrieve the informations about class A and B, but not C.
Somehow (I don't know why) the query try to retrive also the relation between B and C.
And then, with hibernate, start the lazy invocation for retrieving C.
Naturally, if I fetch also the relation between B and C (adding LEFT JOIN FETCH b.c as c) that's not happen.
My question is, why? Why I'm forced to fetch all nested relations and not only the ones which I need?
thank you.
Carmelo
Nullable #OneToOne relation are always eager fetched as explained in this post
Making a OneToOne-relation lazy
Unconstrained (nullable) one-to-one association is the only one that
can not be proxied without bytecode instrumentation. The reason for
this is that owner entity MUST know whether association property
should contain a proxy object or NULL and it can't determine that by
looking at its base table's columns due to one-to-one normally being
mapped via shared PK, so it has to be eagerly fetched anyway making
proxy pointless.
Shot in the dark, but there are some issues with lazy-loading #OneToOne-relationships. At least in older versions of Hibernate. I think (but can't seem to find any documentation) that this was fixed in one of the newer versions, so if you are not using a new version of Hibernate, try upgrading.

Hibernate Search - MySQL error too many joins with Joined Inheritance model

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/

Eager loading a lazy child collection

I have data model similar to this
class Office
{
#ManyToOne
#JoinColumn(name = "cafeteria_id")
Cafeteria cafeteria;
}
class Cafeteria
{
#OneToMany(mappedBy="cafeteria")
#LazyCollection(value=LazyCollectionOption.EXTRA)
List<Chair> chairs;
}
class Chair
{
#ManyToOne
#JoinColumn(name = "cafeteria_id")
Cafeteria cafeteria;
}
I have a JPQL query like this
select o from Office o where o.cafeteria.someThing = ?
Above query works fine but in one case I would like a query which could eagerly load all the chairs(o.cafeteria.chairs) as well. How should I modify query to eagerly fetch all chairs?
Use fetch attribute in OneToMany annotation. You can map the same relationship as many times as you want:
class Cafeteria {
#OneToMany(mappedBy="cafeteria")
#LazyCollection(value=LazyCollectionOption.EXTRA)
List<Chair> chairs;
#OneToMany(fetch = FetchType.EAGER, mappedBy="cafeteria")
List<Chair> eagerlyLoadedChairs;
}
And then you can use any of them:
// Lazy loading
select o from Office o inner join o.cafeteria c inner join c.chairs ch where c.someThing = ?
// Eager loading
select o from Office o inner join o.cafeteria c inner join c.eagerlyLoadedChairsch where c.someThing = ?
You have two options, either you change the fetch type from Lazy to Eager, but this will always load your List of Chair every time you load an object from Cafeteria.
like this:
#OneToMany(mappedBy="cafeteria", fetch=FetchType.EAGER)
#LazyCollection(value=LazyCollectionOption.FALSE)
List<Chair> chairs;
OR In your Service that call this query, after calling it, you simply load the Chair list in your specific use case, check more about Initializing Collections in Hibernate
Hibernate.initialize(cafeteria.getChairs());

Categories

Resources