Hibernate Tips: How to avoid Hibernate’s MultipleBagFetchException
Take your skills to the next level!
The Persistence Hub is the place to be for every Java developer. It gives you access to all my premium video courses, monthly Java Persistence News, monthly coding problems, and regular expert sessions.
Hibernate Tips is a series of posts in which I describe a quick and easy solution for common Hibernate questions. If you have a question for a future Hibernate Tip, please leave a comment below.
Question:
You explained that I should use a JOIN FETCH clause to initialize all associations of my entity that I will use in my use case. But as soon as I do that for more than 1 association, Hibernate throws a MultipleBagFetchException.
How can I JOIN FETCH multiple associations?
Solution:
Hibernate throws a MultipleBagFetchException whenever you try to fetch multiple Bags in a query.
org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple Bags: [org.thoughts.on.java.model.Book.authors, org.thoughts.on.java.model.Book.reviews]
As I explained in my post about the most efficient data type for to-many associations, a Bag is an unordered collection. Hibernate uses it if you model your association as a List.
@Entity public class Book { // Don't do this! @ManyToMany @JoinTable( name="BookAuthor", joinColumns={@JoinColumn(name="bookId", referencedColumnName="id")}, inverseJoinColumns={@JoinColumn(name="authorId", referencedColumnName="id")}) private List authors = new ArrayList(); @OneToMany(mappedBy = "book") private List reviews = new ArrayList(); ... }
You can avoid the MultipleBagFetchException by using a Set instead of a List.
@Entity public class Book { @ManyToMany @JoinTable( name="BookAuthor", joinColumns={@JoinColumn(name="bookId", referencedColumnName="id")}, inverseJoinColumns={@JoinColumn(name="authorId", referencedColumnName="id")}) private Set authors = new HashSet(); @OneToMany(mappedBy = "book") private Set reviews = new HashSet(); ... }
In contrast to the previously used Bags, Hibernate fetches multiple Sets without throwing an exception.
Book b = em.createQuery("SELECT b " + "FROM Book b " + "JOIN FETCH b.authors a " + "JOIN FETCH b.reviews r " + "WHERE b.id = 1", Book.class).getSingleResult();
Learn more:
I explained the differences between a Set and a Bag in more details in How to Choose the Most Efficient Data Type for To-Many Associations – Bag vs. List vs. Set.
And if you want to learn more about Hibernate associations and how you can handle them efficiently, you should read the following posts:
- Ultimate Guide – Association Mappings with JPA and Hibernate
- How to map an association as a java.util.Map
- Best Practices for Many-To-One and One-To-Many Association Mappings
Hibernate Tips Book
Get more recipes like this one in my new book Hibernate Tips: More than 70 solutions to common Hibernate problems.
It gives you more than 70 ready-to-use recipes for topics like basic and advanced mappings, logging, Java 8 support, caching, and statically and dynamically defined queries.
Hi,
this tip avoids the exception, but findBy may then return wrong results!
I had a simular situation with two eager associations. Translated to book example: with a book b, having
two authors a1 and a2 and two reviews r1 and r2.
findBy returned b having two reviews r1 and r2 but four authors a1, a2, and again a1, a2!
The problem seams to be an, in this case, incorrect generated sql like:
select . from book left outer join author . left outer join review ..
Looks to be an error in Hibernate, to use a list and a set together should also be forbidden?!
Hi Hans-Martin,
I wasn’t able to reproduce that issue. But what happens is, that you will get the Book entity 4 times, if it’s associated with 2 Authors and 2 Reviews. You can avoid that by adding the DISTINCT keyword to your query and by setting the QueryHints.PASS_DISTINCT_THROUGH to false, e.g.:
TypedQuery q = em.createQuery(“SELECT DISTINCT b ” b = q.getResultList();
+ “FROM Book b ”
+ “JOIN FETCH b.authors a ”
+ “JOIN FETCH b.reviews r ”
+ “WHERE b.id = 1”,
Book.class);
q.setHint(QueryHints.PASS_DISTINCT_THROUGH, false);
List
Regards,
Thorben
If you change the collection to a Set how will you be able to add multiple new entities to the collection? Even if you add them one by one and have a save call to the child entity, you will run into a problem if the entity manager does not flush between the calls to add the new entities to the parent entity.
Hi Om Tara,
That depends on the equals() implementation of your entity. In most cases, I don’t recommend implementing equals. But if you do, you need to make sure that an entity object with a null primary key value is never equal to another entity object.
Regards,
Thorben