Join fetch fails on fetching 3rd level relationship - java

I have three entities: Person, Country and CountryTranslation.
Person is related to one Country and Country have many CountryTranslations.
I want my query to fetch both Country and CountryTranslations when it fetches a Person in order to avoid multiple queries.
To bring the Country along with Person I do:
List<Person> persons = (List<Person>) entityManager
.createQuery("SELECT person from Person person " +
"left join fetch person.country")
.getResultList()
This works fine, I see on hibernate it is fetching nicely and no extra queries are executed to bring Country, but to bring CountryTranslations it still execute extra queries. Then I tried:
List<Person> persons = (List<Person>) entityManager
.createQuery("SELECT person from Person person " +
"left join fetch person.country " +
"left join fetch person.country.translations")
.getResultList()
And I get the error:
org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list
What is the right way to do this fetching?
Thanks in advance

You correct this giving an alias for each relationship.
SELECT person
FROM person person
LEFT JOIN FETCH person.country country
LEFT JOIN FETCH country.translations
It's a framework's "limitation": When you use linked fetchs you should to give an alias for each relationship!
This behavior can also be explained because will be difficult to understand what these linked fetches really mean: Does the framework will fetch each relationship or only the last one? It's more simple to just tell to the framework what fetch relationship you want.

Related

JPA & Hibernate: Eager loading performing subsequent queries to fetch all data, instead of doing it in just one query

I have the following doubt. I would like to know why when using JPA and Hibernate, when performing an Eager loading in a ManyToOne or OneToMany relationship, it calls the DB in order to obtain the Entity information but additionally, produces subsequent queries to fetch every child.
On the other side, when using a query with JOIN FETCH it performs the query as I would expect it to be, taking the information all at once, since the fetchType is denoted as "EAGER".
Here it is a simple example:
I have the Class Student which has ManyToOne relationship with the Class Classroom.
#Entity
#Table(name = "STUDENT")
public class Student {
#ManyToOne(optional = true, fetch = FetchType.EAGER)
#JoinColumn(name = "ClassroomID")
private Classroom mainClass;
In the other side there is the class named Classroom as follows:
#Entity
public class Classroom {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "mainClass", fetch = FetchType.EAGER)
private List<Student> studentsList;
When obtaining the Classroom object, it performs one query to obtain the information from itself and subsequent queries to obtain the information of each student contained in the studentsList for every classRoom object.
First query:
Hibernate:
/* SELECT
r
FROM
Classroom r
LEFT JOIN
r.classStudents */
select
classroom0_.id as id1_0_,
classroom0_.number as number2_0_
from
Classroom classroom0_
left outer join
STUDENT classstude1_
on classroom0_.id=classstude1_.ClassroomID
And then it performs the next query as many times as Students assigned to each classroom.
Hibernate:
/* load one-to-many com.hw.access.Classroom.classStudents */
select
classstude0_.ClassroomID as Classroo4_0_1_,
classstude0_.id as id1_1_1_,
classstude0_.id as id1_1_0_,
classstude0_.FIRST_NAME as FIRST_NA2_1_0_,
classstude0_.LAST_NAME as LAST_NAM3_1_0_,
classstude0_.ClassroomID as Classroo4_1_0_
from
STUDENT classstude0_
where
classstude0_.ClassroomID=?
The question is: Why doesn't it takes the Information all at once? Why doesn't it takes the information in just one query? As it is already performing the Join clause there.
Why just when adding Fetch explicitly in the query, it does what it is requested?
For example:
SELECT
r
FROM
Classroom r
LEFT JOIN FETCH
r.classStudents */
And then, the output query does takes all the information in just one query:
Hibernate:
select
classroom0_.id as id1_0_0_,
classstude1_.id as id1_1_1_,
classroom0_.number as number2_0_0_,
classstude1_.FIRST_NAME as FIRST_NA2_1_1_,
classstude1_.LAST_NAME as LAST_NAM3_1_1_,
classstude1_.ClassroomID as Classroo4_1_1_,
classstude1_.ClassroomID as Classroo4_0_0__,
classstude1_.id as id1_1_0__
from
Classroom classroom0_
left outer join
STUDENT classstude1_
on classroom0_.id=classstude1_.ClassroomID
As you have a OneToMany relationship from Classroom to Student, using a single query would cause the Classroom fields to be repeated for each line.
Now imagine you have a second OneToMany relationship from Classroom to, say Course; if, for a given Classroom you have N Students and M Courses, you would have a query returning N+M rows, each containing the same fields of class Classroom.
I found it described in https://vladmihalcea.com/eager-fetching-is-a-code-smell/
under EAGER fetching inconsistencies:
Both JPQL and Criteria queries default to select fetching, therefore issuing a secondary select for each individual EAGER association. The larger the associations’ number, the more additional individual SELECTS, the more it will affect our application performance.
Also, note that Hibernate similarly ignoreg fetching annotations for HQL queries:
https://developer.jboss.org/wiki/HibernateFAQ-AdvancedProblems#jive_content_id_Hibernate_ignores_my_outerjointrue_or_fetchjoin_setting_and_fetches_an_association_lazily_using_n1_selects
Hibernate ignores my outer-join="true" or fetch="join" setting and fetches an association lazily, using n+1 selects!
HQL queries always ignore the setting for outer-join or fetch="join" defined in mapping metadata. This setting applies only to associations fetched using get() or load(), Criteria queries, and graph navigation. If you need to enable eager fetching for a HQL query, use an explicit LEFT JOIN FETCH.
By default fetchtype is lazy, that mean if you dont ask for the List in your request Hibernate will not collect it.
In first request you ask for all attribute of Classroom r including the Student list so Hibernate will load them lazily (after finding out that you need them).
But when fetchtype is set to eager hibernate collect it even if you dont ask it.

Spring data jpa from multiple tables

I am using spring data jpa. And I have a inner join on two tables. This is my query.
SELECT A.NAME, A.CARD_NUMBER, A.ADDRESS, A.EMAIL FROM USER_INFO ABC INNER JOIN USR_DETAIL DEF ON (ABC.ID = DEF.ID) WHERE ABC.ID = '123456';
The two table here have no relationship. So one-to-one or many-to-one or many-to-many on the column name doesn't make sense. Can I define entities without relationship? The reason why we are doing a inner join on the two tables here is simply because doing a join on both of them will be a expensive query.
You can define both entities without any kind of relationship and then you can retrieve the data specifying nativeQuery=true in the #Query(..) annotation in the read method.
#Query(value = "SELECT ABC.NAME, ABC.CARD_NUMBER, ABC.ADDRESS, ABC.EMAIL " +
"FROM USER_INFO ABC " +
"INNER JOIN USR_DETAIL DEF ON (ABC.ID = DEF.ID) " +
"WHERE ABC.ID = :id", nativeQuery = true)
UserInfoDetails retrieveUserInfoAndDetailById(#Param("id") String id);
Side notes:
In the projection of the query, I correct the alias from A to ABC as the query was not written correctly. Feel free to edit the projection accordingly to your needs.
As a return type, I wrote a UserInfoDetails class, supposing it will be return something similar. Feel free to change it accordingly to your needs.

How to use multiple JOIN FETCH in one JPQL query

I have below entities:
public class Category {
private Integer id;
#OneToMany(mappedBy = "parent")
private List<Topic> topics;
}
public class Topic {
private Integer id;
#OneToMany(mappedBy = "parent")
private List<Posts> posts;
#ManyToOne
#JoinColumn(name = "id")
private Category parent;
}
public class Post {
private Integer id;
#ManyToOne
#JoinColumn(name = "id")
private Topic parent;
/* Post fields */
}
and I want to fetch all categories with joined topics and joined posts using JPQL query. I wrote query like below:
SELECT c FROM Category c
JOIN FETCH c.topics t
JOIN FETCH t.posts p WHERE
But I got the error
org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags
I found articles about this error, but these articles only describe situation where in one entity are two collections to join. My problem is a little different and I don't know how to solve it.
It is possible to do it in one query?
Considering we have the following entities:
And, you want to fetch some parent Post entities along with all the associated comments and tags collections.
If you are using more than one JOIN FETCH directives:
List<Post> posts = entityManager.createQuery("""
select p
from Post p
left join fetch p.comments
left join fetch p.tags
where p.id between :minId and :maxId
""", Post.class)
.setParameter("minId", 1L)
.setParameter("maxId", 50L)
.getResultList();
Hibernate will throw the MultipleBagFetchException:
org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags [
com.vladmihalcea.book.hpjp.hibernate.fetching.Post.comments,
com.vladmihalcea.book.hpjp.hibernate.fetching.Post.tags
]
The reason why Hibernate throws this exception is that it does not allow fetching more than one bag because that would generate a Cartesian product.
The worst "solution" others might try to sell you
Now, you will find lots of answers, blog posts, videos, or other resources telling you to use a Set instead of a List for your collections.
That's terrible advice. Don't do that!
Using Sets instead of Lists will make the MultipleBagFetchException go away, but the Cartesian Product will still be there, which is actually even worse, as you'll find out the performance issue long after you applied this "fix".
The proper solution
You can do the following trick:
List<Post> posts = entityManager.createQuery("""
select distinct p
from Post p
left join fetch p.comments
where p.id between :minId and :maxId
""", Post.class)
.setParameter("minId", 1L)
.setParameter("maxId", 50L)
.setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
.getResultList();
posts = entityManager.createQuery("""
select distinct p
from Post p
left join fetch p.tags t
where p in :posts
""", Post.class)
.setParameter("posts", posts)
.setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
.getResultList();
In the first JPQL query, distinct DOES NOT go to the SQL statement. That's why we set the PASS_DISTINCT_THROUGH JPA query hint to false.
DISTINCT has two meanings in JPQL, and here, we need it to deduplicate the Java object references returned by getResultList on the Java side, not the SQL side.
As long as you fetch at most one collection using JOIN FETCH, you will be fine.
By using multiple queries, you will avoid the Cartesian Product since any other collection but the first one is fetched using a secondary query.
Always avoid the FetchType.EAGER strategy
If you're using the FetchType.EAGER strategy at mapping time for #OneToMany or #ManyToMany associations, then you could easily end up with a MultipleBagFetchException.
You are better off switching from FetchType.EAGER to Fetchype.LAZY since eager fetching is a terrible idea that can lead to critical application performance issues.
Conclusion
Avoid FetchType.EAGER and don't switch from List to Set just because doing so will make Hibernate hide the MultipleBagFetchException under the carpet. Fetch just one collection at a time, and you'll be fine.
As long as you do it with the same number of queries as you have collections to initialize, you are fine. Just don't initialize the collections in a loop, as that will trigger N+1 query issues, which are also bad for performance.
Here is a working example of complex join and multiple consition:
String query_findByProductDepartmentHospital = "select location from ProductInstallLocation location "
+ " join location.product prod " + " join location.department dep "
+ " join location.department.hospital hos " + " where prod.name = :product "
+ " and dep.name.name = :department " + " and hos.name = :hospital ";
#Query(query_findByProductDepartmentHospital)
ProductInstallLocation findByProductDepartmentHospital(#Param("product") String productName,#Param("department") String departName, #Param("hospital") String hospitalName);
A workaround is to use #Query and #EntityGraph together, like it mentioned here use #Query and #EntityGraph together

JPA left join to find unused entries

I'm sure I'm being stupid but I can't seem to figure this one out...
I have two tables:
department( did, name )
employee( eid, first, last, did )
they have corresponding entities JPA managed entites Department and Employee. Employee has a Deparment field, Department doesn't maintain an Employee list. What I want to do though is find all the Departments that have no Employees. Using plain old SQL this is easy with a left join:
SELECT d.*
FROM department as d LEFT OUTER JOIN employee as e
ON d.did = e.did
WHERE e.did IS NULL
I can't see how to translate this query into JPQL though. All the examples I've found for JPQL left joins traverse the link the other way, for example.
SELECT e FROM Employee e LEFT JOIN e.departmert d
Whereas I need something more like
SELECT d FROM Department d LEFT JOIN d.???? WHERE e.department IS NULL
but the department doesn't maintain a reference to it's employees (in my application it's not departments and employees obviously). Is this even possible in JPQL?
To do what you are trying to do, you would need to setup a mapping from Departments -> Employees (using your example entities). You could used the mappedBy attribute of #OneToMany, which will most likely not disrupt your schema, e.g.,
#Entity
class Department {
...
#OneToMany(mappedBy="employee")
Collection<Employee> getEmployees() {
....
}
...
}
This would allow you to run something like:
SELECT d FROM Department d WHERE d.employees IS EMPTY
Which should give you equivalent results.
Without altering your mapping, you should be able to run something like this query to get the results you want:
SELECT d from Department d WHERE NOT EXIST (SELECT e FROM Employee e where e.department = d)

How to build HQL query, thats joins subtables marked LAZY, automatically?

I have some entity:
public class Album extends GenericAuditedEntity {
#OneToMany(fetch = FetchType.LAZY)
private Set<Item> itemSet = new HashSet<Item>();
}
And when i run HQL like this:
em.createQuery("select a from Album a").getResults()
it produses many SQL queries:
One for select data from Album's table. Smth like this: select .... from Album_table;
And one query for each fetched row, for selecting items. Smth like this:
select .... from Item_table iwhere i.Album_id = :Album_id;
But when i run em.createQuery("
select a.id, b.id
from Album a
left join Item i
").getResults()
it produses one SQL query. But it's result is list of some parameters, that i need put into the entities manually.
How can i build HQL with joins automatically and automatically put the results to the entities? Is it possible?
You need to use join fetch:
em.createQuery("select a.id, b.id from Album a left join fetch Item i ").getResults();
Note that there are certain side effects to that, described in detail the above link.
If you are using join fetch then you don't need the IDs, you can retrieve the Entity as Hibernate will also populate the association in it's first-level cache
em.createQuery("select a from Album a left join fetch a.itemSet").getResultList();
However if you are retrieving the IDs but want populated Objects/Entities then consider using a Constructor
em.createQuery("select new com.xxx.AlbumItem(a.id, b.id) from Album a left join fetch a.itemSet b").getResultList();
dont use lazy fetching. set fetch type to eager

Categories

Resources