Spring Data JPA において MultipleBagFetchException と LazyInitializationException のいずれかの例外が投げられる

More than 1 year has passed since last update.


概要

Spring Data JPA において、下記のような構成のエンティティを作成したところ、MultipleBagFetchException が発生しました。


Parent.java

@Entity

@Table(name = "parent")
@Getter
public class Parent {

@Id
private int id;

@Column(name = "child_id", nullable = false, updatable = false, insertable = false)
private int childId;

@ManyToOne
@JoinColumn(name = "child_id")
private Child child;
}



Child.java

@Entity

@Table(name = "child")
@Getter
public class Child {
@Id
private int id;

@OneToMany(mappedBy = child)
private List<Parent> parents;

@OneToMany(mappedBy = "child", fetch = FetchType.EAGER)
private List<SubChild1> subChild1;

@OneToMany(mappedBy = "child", fetch = FetchType.EAGER)
protected List<SubChild2> subChild2;
}



SubChild1.java


@Entity
@Table(name = "sub_child1")
@Getter
public class SubChild1 {
private Child child;
}


SubChild2.java


@Entity
@Table(name = "sub_child2")
@Getter
public class SubChild2 {
private Child child;
}


error.log

Caused by: org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags: [Parent.child, Child.subChild1, Parent.subChild2]

at org.hibernate.loader.BasicLoader.postInstantiate(BasicLoader.java:75)


詳細

こちらのページと実際の動作から、どうやら、FetchType.EAGER において、Hibernate は 2 層までの Collection のネストを許可していますが、3 層までのネストを許可していないことが問題のようでした。


*JPA allows only two level of nesting for collection, Parententity->Childentity is fine but it does not allow Parententity->childentity->SubChildEntity

*Using List for collection instead of SET -remember a SET guarantees unique elements while list does not


そのため、FetchType.LAZY にてデータを取得しようとした結果、今度は、LazyInitializationException が発生することになりました。

こちらのページ と実際の動作から Lazy で List にアクセスした際、すでに DB のコネクションが閉じてしまっていたために、データの Fetch ができない状況にあるようでした。


どうやらビューでコレクションの要素であるLineItemを遅延初期化(lazily initialize)しようとした時に、 Hinbernateのsessionがクローズしていたので例外が発生みたいです。FetchType.LAZY も立ち行かなくなりました。

そこで、ネット"Spring Hibernate Lazy initialize error" をキーに検索すると

とあるITエンジニアの業務手帖

に同様のケースが報告されていました。

解決策としては、Hibernateマッピングファイルのlazy属性をfalseにするというものでした。 そこで、以下のようにOrderのbagタグにlazy="false"を入れ



解決策

こちらのページ から一部の型を List から Set に変えることで問題が解決できました。


The third solution is to replace java.util.List and java.util.Collection with java.util.Set. We have to think about it - do we really need a List/Collection (a bag)? In many cases the only reason to use a List is that we are used to.


英語力が低く、お恥ずかしい限りですが、Bag は List のように、重複を許容するオブジェクトのようです。

日本語にしてみると、「複数Bags取得例外」となり、まさに現象と一致した動作です。

クラスについては下記のように修正。


Child.java

@Entity

@Table(name = "child")
@Getter
public class Child {
@Id
private int id;

@OneToMany(mappedBy = child)
private Set<Parent> parents;

@OneToMany(mappedBy = "child", fetch = FetchType.EAGER)
private Set<SubChild1> subChild1;

@OneToMany(mappedBy = "child", fetch = FetchType.EAGER)
protected Set<SubChild2> subChild2;
}


補足 : Set について

公式ページ を確認したところ、Set は重複を許可しないオブジェクトのようです。


重複要素のないコレクションです。すなわち、セットは、e1.equals(e2) である e1 と e2 の要素ペアは持たず、null 要素を最大 1 つしか持ちません。その名前が示すように、このインタフェースは、数学で言う集合の抽象化オブジェクトをモデル化します。

Set インタフェースは、Collection インタフェースから継承した規定だけでなく、すべてのコンストラクタの規約、および add、equals、hashCode の各メソッドの規約に追加の規定を適用します。便宜上、ほかの継承メソッドの宣言もここに含まれます。これらの宣言に付随する仕様は Set インタフェースに合わせて調整済みですが、追加規定は含まれていません。


今回の要件では、重複が発生することはありえませんでしたし、NULL も発生しないのでめでたく解決となりました。