ChatGPT解决这个技术问题 Extra ChatGPT

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).

I would ask, what SQL query are you hoping to generate that will retrieve two separate collections simultaneously? The kinds of SQL that would be able to achieve these would either require a cartesian join (potentially highly inefficient) or a UNION of disjoint columns (also ugly). Presumably the inability to achieve this in SQL in a clean & efficient manner influenced the API design.
@ThomasW These are the sql queries it should generate: select * from master; select * from child1 where master_id = :master_id; select * from child2 where master_id = :master_id
You can get a simillar error if you have more than one List<child> with fetchType defined for more than one List<clield>

b
bish

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

But remind that with using sets you won't eliminate the underlaying Cartesian Product as described by Vlad Mihalcea in his answer!


odd, it has worked for me. Did you remove the fetchType from the @*ToMany ?
the problem is that the JPA annotations are parsed not to allow more than 2 eagerly loaded collection. But the hibernate-specific annnotations allow it.
The need for more than 1 EAGER seems totally realistic. Is this limitation just a JPA oversight? What are the concerns I should look for when having muliple EAGERs?
the thing is, hibernate can't fetch the two collections with one query. So when you query for the parent entity, it will need 2 extra queries per result, which is normally something you don't want.
It'd be great to have an explanation as to why this resolves the issue.
b
bish

Simply change from List type to Set type.

But remind that you won't eliminate the underlaying Cartesian Product as described by Vlad Mihalcea in his answer!


A List and a Set are not the same thing: a set does not preserve order
LinkedHashSet preserves order
This is an important distinction and, when you think about it, entirely correct. The typical many-to-one implemented by a foreign key in the DB is really not a List, it's a Set because order is not preserved. So Set is really more appropriate. I think that makes the difference in hibernate, though I don't know why.
I was having the same cannot simultaneously fetch multiple bags but not because of annotations. In my case, I was doing left joins and disjunctions with the two *ToMany. Changing the type to Set solved my problem too. Excellent and neat solution. This should be the official answer.
I liked the answer, but the million dollar question is: Why? Why with Set don't show exceptions? Thanks
D
Dave Richardson

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


@DaveRlz why subSelect solves this problem. I tried your solution and its working, but dont know how the problem was solved by using this ?
This is the best answer unless a Set really makes sense. Having a single OneToMany relationship using a Set results in 1+<# relationships> queries, where as using FetchMode.SUBSELECT results in 1+1 queries. Also, using the annotation in the accepted answer (LazyCollectionOption.FALSE) causes even more queries to be executed.
FetchType.EAGER is not a proper solution for this. Need to proceed with Hibernate Fetch Profiles and need to solve it
The two other top answers did not solve my problem. This one did. Thank you!
Does anyone know why does SUBSELECT fix it, but JOIN does not?
V
Vlad Mihalcea

Considering we have the following entities:

https://i.stack.imgur.com/fVIXG.png

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 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.

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.


Vlad, thanks for the help I find it really usuful. However, the issue was related to hibernate.jdbc.fetch_size (eventually I set it to 350). By the chance, do you know how to optimize nested relations? E.g. entity1 -> entity2 -> entity3.1, entity 3.2 (where entity3.1 / 3.2 are @OneToMany relations)
No, you can not. Think about it in terms of SQL. You cannot JOIN multiple one-to-many associations without generating a Cartesian Product.
You should always havd @Transactional on the service methods calling a Spring Data Jpa Repository. Not doing so is a terrible mistake.
Of course. The query is more obvious and doesn't have the IN clause limit issue.
I was attempting to use this with @NamedEntityGraph annotated entity classes. It does not work, unless I remove the graph logic. Is there any merit to the entity graph technique?
A
Abimaran Kugathasan

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


adding @Fetch(value = FetchMode.SUBSELECT) was enough
This is a Hibernate only solution. What if you are using a shared JPA library?
I'm sure you didnt mean to, but DaveRlz already wrote the same thing 3 years earlier
b
bish

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 will work.

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

But remind that you won't eliminate the underlaying Cartesian Product as described by Vlad Mihalcea in his answer!


Both recommendations you made are very bad in terms of performance.
C
Christian Müller

I found a good Blog post about the behaviour of Hibernate in this kind of object mappings: http://blog.eyallupu.com/2010/06/hibernate-exception-simultaneously.html


Welcome to Stack Overflow. However, link-only answers are generally discouraged. See the faq, and particularly: stackoverflow.com/faq#deletion.
M
Massimo

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


r
rimsky

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.


F
Felipe Cadena

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.


佚名

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());

a
alxkudin

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


J
JAD

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


p
prisar

Commenting both Fetch and LazyCollection sometimes helps to run project.

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

W
WesternGun

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.


A
Antonio Petricca

I solved by annotating:

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

K
Karen Hechter

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());


l
leee41

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.


JPA3.0 does not exist.

关注公众号,不定期副业成功案例分享
Follow WeChat

Success story sharing

Want to stay one step ahead of the latest teleworks?

Subscribe Now