blow
blow

Reputation: 13209

Hibernate throws MultipleBagFetchException - cannot simultaneously fetch multiple bags

Hibernate throws this exception during SessionFactory creation:

org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags

This is my test case:

Parent.java

@Entity
public Parent {

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Long id;

 @OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
 // @IndexColumn(name="INDEX_COL") if I had this the problem solve but I retrieve more children than I have, one child is null.
 private List<Child> children;

}

Child.java

@Entity
public Child {

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Long id;

 @ManyToOne
 private Parent parent;

}

How about this problem? What can I do?


EDIT

OK, the problem I have is that another "parent" entity is inside my parent, my real behavior is this:

Parent.java

@Entity
public Parent {

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Long id;

 @ManyToOne
 private AnotherParent anotherParent;

 @OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
 private List<Child> children;

}

AnotherParent.java

@Entity
public AnotherParent {

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Long id;

 @OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
 private List<AnotherChild> anotherChildren;

}

Hibernate doesn't like two collections with FetchType.EAGER, but this seems to be a bug, I'm not doing unusual things...

Removing FetchType.EAGER from Parent or AnotherParent solves the problem, but I need it, so real solution is to use @LazyCollection(LazyCollectionOption.FALSE) instead of FetchType (thanks to Bozho for the solution).

Upvotes: 627

Views: 505165

Answers (18)

Vlad Mihalcea
Vlad Mihalcea

Reputation: 154070

Considering we have the following entities:

enter image description here

And, you want to fetch some parent Post entities along with all the 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 infamous:

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
]

Hibernate doesn't allow fetching more than one bag because that would generate a Cartesian product.

The worst "solution"

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 Hibernate 6 solution

If you're using Hibernate 6, then you can fix this issue like this:

List<Post> posts = entityManager.createQuery("""
    select p
    from Post p
    left join fetch p.comments
    where p.id between :minId and :maxId
    """, Post.class)
.setParameter("minId", 1L)
.setParameter("maxId", 50L)
.getResultList();

posts = entityManager.createQuery("""
    select p
    from Post p
    left join fetch p.tags t
    where p in :posts 
    """, Post.class)
.setParameter("posts", posts)
.getResultList();

As long as you fetch at most one collection using JOIN FETCH per query, 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.

The proper Hibernate 5 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.

By using multiple queries, you will avoid the Cartesian Product since any other collection, but the first one is fetched using a secondary query.

There's more you could do

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.

Upvotes: 230

Bozho
Bozho

Reputation: 597344

I think a newer version of hibernate (supporting JPA 2.0) should handle this. But otherwise you can work it around by annotating the collection fields with:

@LazyCollection(LazyCollectionOption.FALSE)

Remember to remove the fetchType attribute from the @*ToMany annotation.

But note that in most cases a Set<Child> is more appropriate than List<Child>, so unless you really need a List - go for Set

Use with caution

Remember that using a Set won't eliminate the underlying Cartesian Product as described by Vlad Mihalcea in his answer, featured as "The worst solution"!

Upvotes: 691

Prateek Singh
Prateek Singh

Reputation: 1114

To fix it simply take Set in place of List for your nested object.

@OneToMany
Set<Your_object> objectList;

And don't forget to use fetch=FetchType.EAGER, It'll work.

There is one more concept CollectionId in Hibernate if you want to stick with list only.

Use with caution

Remember that you won't eliminate the underlying Cartesian Product as described by Vlad Mihalcea in his answer, featured as "The worst solution"!

Upvotes: 17

Ahmad Zyoud
Ahmad Zyoud

Reputation: 3578

Simply change from List type to Set type.

Use with caution

This solution is not recommended as it won't eliminate the underlying Cartesian Product as described by Vlad Mihalcea in his answer, featured as "The worst solution"!

Upvotes: 333

Mario B
Mario B

Reputation: 2355

TLDR: Don't use EAGER. Always fetch things only when you really need them!


A lot of mentions here about FetchType.EAGER and I wanted to go a bit more into detail why this is a bad idea and on what to use alternatively. Hopefully after reading this you realize that really you should absolutely NEVER use FetchType.EAGER.

There is just no good reason to! It's a mayor pitfall that can bite you (or even worse: someone else) down the road. Imagine you chose FetchType.EAGER for a Student -> Teacher relation, because you think every time you need to fetch a Student you also need his Teachers. Now even if you are 100% sure you never need students without teachers right now, you simply can't foresee how requirements change. FetchType.EAGER violates the Open-closed principle! Your code is no longer open for extension - if later the need for loading Students without Teachers arise it's difficult do to that without reworking (and possibly breaking) existing code!

An even bigger problem you have is that you basically created an n+1 select problem for possible future queries, that (rightfully) already fetched another bag in the basic query. Let's say in the future someone wants to load all Students with their grades and there are 30000 of them. Since you told Hibernate to EAGER fetch all Teachers it has to do that. But since you already fetched another "bag" (the grades) within the same query this actually results in 30001 queries - for data that wasn't even needed in that scenario! The first query for loading all Students+Grades and then a separate query for each Student to fetch his teachers. Needless to say that this is horrendous for performance. In my opinion this is the sole reason for people to believe that "Hibernate is slow" - they just don't realize how incredible inefficient it might query stuff in some situations. You really have to be careful with 1:n relations.

3 Alternatives (manually fetch stuff when needed):

  1. Use JOIN FETCH to fetch a collection. To stick with the example SELECT stud FROM STUDENT stud JOIN FETCH stud.teachers would do the trick. This will always fire a single query to fetch students AND teachers. Just be mindful to only fetch one collection that way (explanation would go too far for this answer).
  2. If you use Spring-JPA you can use @EntityGraph(attributePaths = {"teachers"}) to do the same.
  3. You could call Hibernate.initialize(oneStudent.getTeachers()) with the Hibernate-proxy to fetch the relation manually. This will always create a separate query, so don't do it in a loop or you just created a n+1 select problem yourself.

Also one final tip in general: turn the logging for org.hibernate.SQL to DEBUG to see when/what queries are fired and make sure your local dev setup has a reasonable amount of data. When you test your code with only 2 students and don't check the queries that are fired you might miss something like that and end up having issues on real systems with more data.

Upvotes: 0

alxkudin
alxkudin

Reputation: 11

You can also try to make fetch=FetchType.LAZY and just add @Transactional(readOnly = true) to method where you get child

Upvotes: 1

Karen Hechter
Karen Hechter

Reputation: 21

Ok so here's my 2 cents. I had the Fetch Lazy annotations in my Entity but I also duplicated the fetch lazy in the session bean thus causing a multiple bag issue. So I just removed the lines in my SessionBean

criteria.createAlias("FIELD", "ALIAS", JoinType.LEFT_OUTER_JOIN); //REMOVED

And I used Hibernate.initialize on the list I wanted to retrieve after the List parent = criteria.list was called. Hibernate.initialize(parent.getChildList());

Upvotes: 0

Antonio Petricca
Antonio Petricca

Reputation: 11040

I solved by annotating:

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)

Upvotes: 0

WesternGun
WesternGun

Reputation: 12787

One good thing about @LazyCollection(LazyCollectionOption.FALSE) is that several fields with this annotation can coexist while FetchType.EAGER cannot, even in the situations where such coexistence is legit.

For example, an Order may have a list of OrderGroup(a short one) as well as a list of Promotions(also short). @LazyCollection(LazyCollectionOption.FALSE) can be used on both without causing LazyInitializationException neither MultipleBagFetchException.

In my case @Fetch did solve my problem of MultipleBacFetchException but then causes LazyInitializationException, the infamous no Session error.

Upvotes: 0

prisar
prisar

Reputation: 3195

Commenting both Fetch and LazyCollection sometimes helps to run project.

@Fetch(FetchMode.JOIN)
@LazyCollection(LazyCollectionOption.FALSE)

Upvotes: 0

JAD
JAD

Reputation: 1160

At my end, this happened when I had multiple collections with FetchType.EAGER, like this:

@ManyToMany(fetch = FetchType.EAGER, targetEntity = className.class)
@JoinColumn(name = "myClass_id")
@JsonView(SerializationView.Summary.class)
private Collection<Model> ModelObjects;

Additionally, the collections were joining on the same column.

To solve this issue, I changed one of the collections to FetchType.LAZY since it was okay for my use-case.

Goodluck! ~J

Upvotes: 1

Dave Richardson
Dave Richardson

Reputation: 5005

Add a Hibernate-specific @Fetch annotation to your code:

@OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
@Fetch(value = FetchMode.SUBSELECT)
private List<Child> childs;

This should fix the issue, related to Hibernate bug HHH-1718

Upvotes: 188

user10812734
user10812734

Reputation:

For me, the problem was having nested EAGER fetches.

One solution is to set the nested fields to LAZY and use Hibernate.initialize() to load the nested field(s):

x = session.get(ClassName.class, id);
Hibernate.initialize(x.getNestedField());

Upvotes: 1

Mike Dudley
Mike Dudley

Reputation: 67

We tried Set instead of List and it is a nightmare: when you add two new objects, equals() and hashCode() fail to distinguish both of them ! Because they don't have any id.

typical tools like Eclipse generate that kind of code from Database tables:

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((id == null) ? 0 : id.hashCode());
    return result;
}

You may also read this article that explains properly how messed up JPA/Hibernate is. After reading this, I think this is the last time I use any ORM in my life.

I've also encounter Domain Driven Design guys that basically say ORM are a terrible thing.

Upvotes: 2

leee41
leee41

Reputation: 1

You could use a new annotation to solve this:

@XXXToXXX(targetEntity = XXXX.class, fetch = FetchType.LAZY)

In fact, fetch's default value is FetchType.LAZY too.

Upvotes: -5

Massimo
Massimo

Reputation: 1239

you can keep booth EAGER lists in JPA and add to at least one of them the JPA annotation @OrderColumn (with obviously the name of a field to be ordered). No need of specific hibernate annotations. But keep in mind it could create empty elements in the list if the chosen field does not have values starting from 0

 [...]
 @OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
 @OrderColumn(name="orderIndex")
 private List<Child> children;
 [...]

in Children then you should add the orderIndex field

Upvotes: 7

Felipe Cadena
Felipe Cadena

Reputation: 121

When you have too complex objects with saveral collection could not be good idea to have all of them with EAGER fetchType, better use LAZY and when you really need to load the collections use: Hibernate.initialize(parent.child) to fetch the data.

Upvotes: 3

Javier
Javier

Reputation: 607

After trying with every single option describe in this posts and others, I came to the conclusion that the the fix is a follows.

In every XToMany place @XXXToMany(mappedBy="parent", fetch=FetchType.EAGER) and intermediately after

@Fetch(value = FetchMode.SUBSELECT)

This worked for me

Upvotes: 49

Related Questions