xrcwrn
xrcwrn

Reputation: 5327

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

Following is my code Here I am using multiple lists to fetch data from database. On fetching data from hql query it is showing exception.

Pojo Class

public class BillDetails implements java.io.Serializable {

private Long billNo;
// other fields
@LazyCollection(LazyCollectionOption.FALSE)
private List<BillPaidDetails> billPaidDetailses = new ArrayList<BillPaidDetails>();
private Set productReplacements = new HashSet(0);
@LazyCollection(LazyCollectionOption.FALSE)
private List<BillProduct> billProductList = new ArrayList<BillProduct>();
//getter and setter
}

hmb.xml file

<class name="iland.hbm.BillDetails" table="bill_details" catalog="retail_shop">
        <id name="billNo" type="java.lang.Long">
            <column name="bill_no" />
            <generator class="identity" />
        </id>
 <bag name="billProductList" table="bill_product" inverse="true" lazy="false" fetch="join">
            <key>
                <column name="bill_no" not-null="true" />
            </key>
            <one-to-many class="iland.hbm.BillProduct" />
        </bag>
        <bag name="billPaidDetailses" table="bill_paid_details" inverse="true" lazy="false" fetch="select">
            <key>
                <column name="bill_no" not-null="true" />
            </key>
            <one-to-many class="iland.hbm.BillPaidDetails" />
        </bag>
        <set name="productReplacements" table="product_replacement" inverse="true" lazy="false" fetch="join">
            <key>
                <column name="bill_no" not-null="true" />
            </key>
            <one-to-many class="iland.hbm.ProductReplacement" />
        </set>
    </class>

Hql query

String hql = "select distinct bd,sum(bpds.amount) from BillDetails as bd "
                    + "left join fetch bd.customerDetails as cd "
                    + "left join fetch bd.billProductList as bpd "
                    + "left join fetch bpd.product as pd "
                    +"left join fetch bd.billPaidDetailses as bpds "
                    + "where bd.billNo=:id "
                    + "and bd.client.id=:cid ";

I am trying following query to fetch data from database but this is showing org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags How to resolve this

Upvotes: 22

Views: 73105

Answers (7)

Vlad Mihalcea
Vlad Mihalcea

Reputation: 153800

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.

Upvotes: 33

alibttb
alibttb

Reputation: 545

I find using @PostLoad annotated method in the entity most useful, I'd do something like

@PostLoad
public void loadCollections(){
     int s1 = productReplacements.size();
     int s2 = billProductList.size();
}

this way I'm able to fine control the eager loading and initialization of collections in the same transaction that loaded the entity.

Upvotes: 1

hakson
hakson

Reputation: 146

I used the new annotation @OrderColumn instead of @IndexColumn (deprecated see: https://docs.jboss.org/hibernate/orm/5.2/javadocs/org/hibernate/annotations/IndexColumn.html) and it works now.

Annotate one of the collections with @OrderColumn e.g.

@ManyToMany(cascade = CascadeType.ALL)
@OrderColumn
private List<AddressEntity> addresses = Lists.newArrayList();

@Builder.Default
@ManyToMany(cascade = CascadeType.ALL)
private List<BankAccountEntity> bankAccounts = Lists.newArrayList();

Upvotes: 0

BERGUIGA Mohamed Amine
BERGUIGA Mohamed Amine

Reputation: 6280

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;

Upvotes: 28

enlait
enlait

Reputation: 616

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.

Upvotes: 2

L. Holanda
L. Holanda

Reputation: 4631

Changing to Set is the best solution. However, if you cannot not replace the List with Set (like in my case, there was a heavy use of JSF tags specific to Lists), and if you can use Hibernate proprietary annotations, you can specify @IndexColumn (name = "INDEX_COL"). That solution worked better for me, changing to Set would require tons of refactoring.

So, your code would be something like this:

@IndexColumn (name = "INDEX_COL")
private List<BillPaidDetails> billPaidDetailses = new ArrayList<BillPaidDetails>();

@IndexColumn (name = "INDEX_COL")
private List<BillProduct> billProductList = new ArrayList<BillProduct>();

As Igor suggested in the comments, you could also create proxy methods to return the lists. I haven't tried that, but would be a good alternative if you cannot use Hibernate proprietary annotations.

Upvotes: 2

Pracede
Pracede

Reputation: 4361

Your request fetch too many data and HIbernate cannot load them all. Reduce your request and/or configure your entities to retrieve just needed data

Upvotes: -3

Related Questions