Keywords: Hibernate | MultipleBagFetchException | Collection Loading
Abstract: This article provides a comprehensive analysis of the Hibernate MultipleBagFetchException, exploring solutions including @LazyCollection annotation, collection type selection, and multi-query strategies, with detailed code examples illustrating implementation details and applicable scenarios.
Problem Background and Exception Analysis
During Hibernate application development, when attempting to fetch multiple collection associations simultaneously, developers often encounter the org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags exception. The root cause of this exception lies in Hibernate's inability to safely handle the simultaneous loading of multiple unordered collections (referred to as "bags"), as this may lead to Cartesian product issues and data duplication.
Typical Scenarios for Exception Occurrence
Consider the following entity relationship model: a parent entity contains two one-to-many associations with child entity collections. When both collections are configured for eager loading (FetchType.EAGER), Hibernate throws MultipleBagFetchException during session factory creation.
Example entity definitions:
@Entity
public class Parent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
private AnotherParent anotherParent;
@OneToMany(mappedBy = "parent")
private List<Child> children;
}
@Entity
public class AnotherParent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "parent")
private List<AnotherChild> anotherChildren;
}This configuration causes Hibernate to be unable to initialize two List-type collection associations simultaneously.
Core Solution: @LazyCollection Annotation
The most direct and effective solution is to use Hibernate-specific @LazyCollection annotation. This approach allows explicit control over collection loading behavior while avoiding MultipleBagFetchException.
Implementation code:
@Entity
public class Parent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "parent")
@LazyCollection(LazyCollectionOption.FALSE)
private List<Child> children;
}
@Entity
public class AnotherParent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "parent")
@LazyCollection(LazyCollectionOption.FALSE)
private List<AnotherChild> anotherChildren;
}It's important to note that when using @LazyCollection(LazyCollectionOption.FALSE), the fetch attribute should be removed from the @OneToMany annotation, as the loading behavior is now controlled by the @LazyCollection annotation.
Considerations for Collection Type Selection
Another common solution is changing the collection type from List to Set. While this approach superficially eliminates MultipleBagFetchException, its potential impacts require careful consideration.
Example using Set:
@Entity
public class Parent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "parent")
private Set<Child> children;
}Although this method avoids the exception, it doesn't resolve the underlying Cartesian product issue. When dealing with large associated datasets, this solution may lead to severe performance problems, as the database still needs to execute SQL queries that generate Cartesian products.
Multi-Query Strategy Solution
For scenarios requiring simultaneous loading of multiple collections, a superior solution is adopting a multi-query strategy. This approach loads different collection associations in separate steps, effectively avoiding Cartesian product issues.
Hibernate 6 implementation example:
// Step 1: Load main entities and first collection
List<Parent> parents = entityManager.createQuery("""
select p
from Parent p
left join fetch p.children
where p.id between :minId and :maxId
""", Parent.class)
.setParameter("minId", 1L)
.setParameter("maxId", 50L)
.getResultList();
// Step 2: Load second collection
parents = entityManager.createQuery("""
select distinct p
from Parent p
left join fetch p.anotherChildren
where p in :parents
""", Parent.class)
.setParameter("parents", parents)
.getResultList();For Hibernate 5, additional handling of DISTINCT propagation is required:
List<Parent> parents = entityManager.createQuery("""
select distinct p
from Parent p
left join fetch p.children
where p.id between :minId and :maxId
""", Parent.class)
.setParameter("minId", 1L)
.setParameter("maxId", 50L)
.setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
.getResultList();
parents = entityManager.createQuery("""
select distinct p
from Parent p
left join fetch p.anotherChildren
where p in :parents
""", Parent.class)
.setParameter("parents", parents)
.setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
.getResultList();Performance Optimization Recommendations
In practical applications, excessive use of eager loading strategies should be avoided. While eager loading reduces additional queries caused by lazy loading, it can easily lead to performance issues when dealing with complex object graphs.
Recommended practices include:
- Prefer lazy loading (
FetchType.LAZY) as the default strategy - Explicitly load associated data using
JOIN FETCHwhen needed - Adopt multi-query strategies for scenarios requiring simultaneous loading of multiple collections
- Monitor query performance to avoid N+1 query problems
Conclusion
MultipleBagFetchException serves as an important protective mechanism in Hibernate, alerting developers to potential performance issues. By appropriately using the @LazyCollection annotation, selecting suitable collection types, and adopting multi-query strategies, this problem can be effectively resolved. Most importantly, developers should deeply understand the underlying principles of various solutions and choose the most appropriate implementation based on specific business scenarios.