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
Related
Following entities:
#Table
class AA1 {
#Id
Long id;
String a_number;
Category category;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name= 'a_number', referencedColumnName='a_number')
#JoinColumn(name= 'category', referencedColumnName='category')
BB1 bb1
...other fields...
}
#Table
class BB1 {
#Id
String a_number;
Category category;
String value;
}
JPQL query:
SELECT a FROM AA1 a LEFT JOIN a.bb1 b;
Hibernate produces correct sql query, but when it tries to collect data it makes additional call like:
SELECT b.a_number, b.category, b.value FROM BB1 b WHERE b.a_number = ? AND b.category = ?
I checked that query returns null.
How can I avoid such database queries?
My investigation: As far as I see Hibernate creates key by(AA1.a_number and AA1.category) and tries to retrieve entity from context. And for specific row 'left join' query returns null values and Hibernate asks context by key and context returns null, it leads to call to database for it.
You must add FETCH to your JPQL query :
SELECT a FROM AA1 a LEFT JOIN FETCH a.bb1 b;
but keep the LAZY loading, because Hibernante will always try to get ManyToOne or OneToOne relationship, which are EAGER by default, with an additional query.
Look at this article https://thorben-janssen.com/5-common-hibernate-mistakes-that-cause-dozens-of-unexpected-queries/ from Th
By default, to make lazy loading work for #OneToOne and #ManyToOne, you need to enable "no proxy lazy associations". Otherwize, despite the FetchType.LAZY annotation, the associated object will be "fetched" and the "fetch" will be done with an extra sql query.
Therefore, one half-way solution to leverage performances without enabling "no proxy lazy associations" is to avoid extra queries by forcing a join fetch on the associated objet. Various technics allow to reach this goal : "LEFT JOIN FETCH" in JPQL queries or EntityGraph.
Just looking at the entity definition #ManyToOne(fetch = FetchType.LAZY) is the cause.
You are explicitly telling JPA to fetch BB1 only when it is needed/accessed.Hence when the first call to get the parent entity is made BB1 is not loaded.It is only when you are accessing the child that triggers JPA to fetch it.
If you change it to FetchType.EAGER , both the entities will be queried in a single call.
But be careful there are advantages/pit-falls with either approach.
Read more abt it here : https://thorben-janssen.com/entity-mappings-introduction-jpa-fetchtypes/
I am trying to create a one to many query. the join seems to be working because the set gets populated but when i add a query to the set it doesn't seem to do anything
Here is one class:
#OneToMany(fetch=FetchType.LAZY, mappedBy="parent")
public Set<Child> getChildren() {
return this.children;
}
Here is the other
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="PARENT_ID")
public Parent getParent() {
return this.parent;
}
and here is the not working query
Criteria criteria = session.createCriteria(Parent.class,"p")
.setFetchMode("p.children", FetchMode.JOIN)
.createAlias("p.children", "c")
.add(Restrictions.like("p.name", "%" + nameQuery + "%").ignoreCase())
.add(Restrictions.eq("c.gender", "boy"));
and this query gets me all my parents by name correnctly and their all children, but i only want the boy children and yet it gives me all the girls too. anyone see what i am doing wrong?
As per the docs,
Join fetching: Hibernate retrieves the associated instance or collection
in the same SELECT, using an OUTER JOIN.
In an outer join, you will get all the records in both the tables irrespective of the join condition, so change your fetching strategy to SELECT and it should fetch only the results matching the restriction. Also I'd recommend to enable batch fetching if you're not doing that already.
How do I get JPA & JPQL to pass a complete join query to the RDBMS? For example,
SELECT e
FROM Employee e
WHERE a.runkey = e.runkey
AND e.middle = 'M'
AND a.state = 'MA'
With the following Employee class
#Entity
public class Employee implements Serializable {
blah ... blah
#OneToOne
#JoinColumn(
name = "runkey",
referencedColumnName = "runkey",
insertable=false, updatable=false)
private Address address;
}
and the JPQL,
SELECT e
FROM Employee e
INNER JOIN FETCH e.address AS a
WHERE a.state = :state
AND e.middle = :middle
I am able to get Hibernate JPA to pull the data as expected.
However, eclipselink croaks that it cannot traverse associated field "address".
If so, how then should I design the Employee entity and how should I phrase the JPQL in order to get eclipselink to execute a table join with WHERE filters on both tables?
(Rant: Otherwise Eclipselink JPA is no better than JDO!!!)
~
Further edit: Does this post mean anything to my case ....
https://forums.oracle.com/forums/thread.jspa?threadID=1568659
The problem is that you are trying to alias a join fetch which is not allowed according to the JPQL specs. Hibernate allows this anyway.
You can still obtain the desired behavior with EclipseLink using query hints.
Take a look at the following posts:
jpa fetch join query
EclipseLink JPQL (Glassfish v3): join fetch syntax problem?
The following link can also be useful:
http://wiki.eclipse.org/EclipseLink/Examples/JPA/QueryOptimization
I have 3 tables, Role[roleId, roleName], Token[tokenID, tokenName] & ROLETOKENASSOCIATION[roleId, tokenID]. The 3rd one was created automatically by hibernate. Now if i simply write a Query to get all the objects from Role class means, it gives the all role objects along with the associated tokenID & tokenName.
I just wanted the association as unidirectional. i.e: Roles--->Tokens
So the annotation in the Role class looks like,
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int roleId;
private String roleName;
#ManyToMany
#JoinTable(name="ROLE_TOKEN_ASSOCIATION",
joinColumns={#JoinColumn(name="roleId")},
inverseJoinColumns={#JoinColumn(name="tokenID")})
private List<Token> tkns;
//Getters & Setters
Now i want the tokenNames for the specific roleId.
First i made a query like this SELECT tkns.tokenName FROM Role WHERE Role.roleId=:roleId
But, i ended up with some dereference error.
Then i changed the query to SELECT tkns FROM Role r WHERE r.roleId=:roleId
Now i have got what i wanted. But it comes with roleId too.
How shall i get tokenName itself?
Actually my problem is solved, but i would like to know how to do it.
It ll be helpful to me, if anyone explains the Query Construction.
Any suggestions!!
Have you tried
SELECT t.tokenName FROM Role r JOIN r.tkns t WHERE r.roleId = :roleId
EDIT: This query almost directly maps to the corresponding SQL query where Role r JOIN r.tkns t is a shorthand syntax for the SQL join via the link table Role r JOIN ROLETOKENASSOCIATION rt ON r.roleId = rt.roleId JOIN Token ON rt.tokenId = t.tokenId. Affe's answer is another syntax for the same query.
See also:
Chapter 14. HQL: The Hibernate Query Language
You want a scalar list of just the name field? You should be able to get that like this
select t.name from Roles r, IN(r.tkns) t where r.roleId = :id
I'm piggy-backing off of How to join tables in unidirectional many-to-one condition?.
If you have two classes:
class A {
#Id
public Long id;
}
class B {
#Id
public Long id;
#ManyToOne
#JoinColumn(name = "parent_id", referencedColumnName = "id")
public A parent;
}
B -> A is a many to one relationship. I understand that I could add a Collection of Bs to A however I do not want that association.
So my actual question is, Is there an HQL or Criteria way of creating the SQL query:
select * from A left join B on (b.parent_id = a.id)
This will retrieve all A records with a Cartesian product of each B record that references A and will include A records that have no B referencing them.
If you use:
from A a, B b where b.a = a
then it is an inner join and you do not receive the A records that do not have a B referencing them.
I have not found a good way of doing this without two queries so anything less than that would be great.
Thanks.
I've made an example with what you posted and I think this may work:
select a,b from B as b left outer join b.parent as a in HQL.
I have to find a "criteria" way of doing that though.
You may do so by specifying the fetch attribute.
(10) fetch (optional) Choose between outer-join fetching and fetching by sequential select.
You find it at: Chapter 6. Collection Mapping, scroll down to: 6.2. Mapping a Collection
EDIT
I read in your question's comment that you wanted a way to perform a raw SQL query? Here a reference that might possibly be of interest:
Chapter 13 - Native SQL Queries
and if you want a way to make it possible through HQL:
Chapter 11. HQL: The Hibernate Query Language
In chapter 11, you want to scroll down to 11.3. Associations and joins.
IQuery q = session.CreateQuery(#"from A as ClassA left join B as ClassB");
I guess however that ClassB needs to be a member of ClassA. Further reasdings shall help.
Another thing that might proove to be useful to you are named queries:
<query name="PeopleByName">
from Person p
where p.Name like :name
</query>
And calling this query from within code like so:
using (var session = sessionFactory.OpenSession())
using (var tx = session.BeginTransaction()) {
session.GetNamedQuery("PeopleByName")
.SetParameter("name", "ayende")
.List();
tx.Commit();
}
Please take a look at the referenced link by Ayende who explains it more in depth.