Asked  7 Months ago    Answers:  5   Viewed   310 times

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

 Answers

32

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!

Tuesday, June 1, 2021
 
csi
answered 7 Months ago
csi
70

The REMOVE cascade type is for the standard JPA remove() operation. For the native Hibernate delete() operation, you need to use a Hibernate-proprietary annotation:

@Cascade(CascadeType.DELETE)
Thursday, July 29, 2021
 
shivam
answered 5 Months ago
44

This is a rather nasty problem in Hibernate and actually ORM in general.

What happens is that the many (fetch) joins cause a rather large cartesian product to be created. I.e for ever other join new columns and new rows appear in the result, leading to a (fairly) large 'square' result.

Hibernate needs to distill a graph from this table, but it's not smart enough to match the right columns to the right entities.

E.g.

Suppose we have the result

A B C
A B D

Which needs to become:

 A
 |
 B
 /
C  D

Hibernate could deduct from the primary keys and some encoding magic, what the graph must be, but in practice it needs explicit help to pull this off.

One way to do this is by specifying the Hibernate specific @IndexColumn or the JPA standard @OrderColumn on the relations.

E.g.

@Entity
public class Question {


    @ManyToMany
    @JoinTable(
        name = "question_to_answer",
        joinColumns = @JoinColumn(name = "question_id"),
        inverseJoinColumns = @JoinColumn(name = "answer_id")
    )
    @IndexColumn(name = "answer_order")
    private List<Answer> answers;

    // ...
}

In this example I'm using a join table, with an extra column answer_order. Via this column, which has a unique sequential number per Question/Answer relation, Hibernate can distinguish the entries in the result table and create the required Object graph.

One note btw, if it concerns more than a few entities, using so many eager joins can potentially lead to a much larger result set than you might think based on the number of entities involved.

Further reading:

  • Hibernate Exception - Simultaneously Fetch Multiple Bags 1
  • Hibernate Exception - Simultaneously Fetch Multiple Bags 2
  • Solving simultaneously fetch multiple bags - Using the @IndexColumn
Sunday, August 1, 2021
 
Kaj Lindberg
answered 5 Months ago
92

I encountered this error all the time.

Just put an inverse="true" on the relationship and your problem will go away!

<list name="charityTransferItemList" inverse="true" cascade="all-delete-orphan" lazy="false" >
        <key column="TSF_NO" />
        <list-index column="TSF_SEQ_NO"/>
        <one-to-many class="CharityTransferItem" />
    </list>

Basically the inverse will tell hibernate the child cannot exist without a parent, thereby causing hibernate to delete the child.

Having said that, you'll also need to remove the charityTransfer object from the collection in the parent as well.

Tuesday, August 3, 2021
 
answered 5 Months ago
80

Here's the list of rules you should follow, in order to be able to store a parent entity along with its children in a one shot:

  • cascade type PERSIST should be enabled (CascadeType.ALL is also fine)
  • a bidirectional relationship should be set correctly on both sides. E.g. parent contains all children in its collection field and each child has a reference to its parent.
  • data manipulation is performed in the scope of a transaction. NO AUTOCOMMIT MODE IS ALLOWED.
  • only parent entity should be saved manually (children will be saved automatically because of the cascade mode)

Mapping issues:

  • remove @Column(name="id") from both entities
  • make setter for cartItems private. Since Hibernate is using its own implementation of the List, and you should never change it directly via setter
  • initialize you list private List<CartItem> cartItems = new ArrayList<>();
  • use @ManyToOne(optional = false) instead of nullable = false inside the @JoinColumn
  • prefer fetch = FetchType.LAZY for collections
  • it's better to use helper method for setting relationships. E.g. class Cart should have a method:

    public void addCartItem(CartItem item){
        cartItems.add(item);
        item.setCart(this);
    }
    

Design issues:

  • it's not good to pass DTOs to the DAO layer. It's better to do the conversion between DTOs and entities even above the service layer.
  • it's much better to avoid such boilerplate like method save with Spring Data JPA repositories
Monday, August 9, 2021
 
laurent
answered 4 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :
 
Share