LoginSignup
7
7

More than 5 years have passed since last update.

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

Posted at

概要

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 も発生しないのでめでたく解決となりました。

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7