org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags
Hibernate doesn't allow fetching more than one bag because that would generate a Cartesian product.
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!
Using Sets
instead of Lists
will make the MultipleBagFetchException
go away, but the Cartesian Product will still be there.
The right fix
Instead of using multiple JOIN FETCH
in a single JPQL or Criteria API query:
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();
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();
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.
For me I had the same error and I solved by adding the annotation of hibernate @Fetch
@OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
@Fetch(value = FetchMode.SUBSELECT)
private List<Child> childs;
You can only join-fetch following one relation for an entity (either billPaidDetailses
or billProductList
).
Consider using lazy associations and loading collections when they are needed, OR using lazy associations and loading collections manually with Hibernate.initialize(..)
. At least that was the conclusion I came to when I had a similar issue.
Either way it will take more than one query to the database.