HibernateがMultipleBagFetchExceptionをスローします-複数のバッグを同時にフェッチできません


471

Hibernateは、SessionFactoryの作成中にこの例外をスローします。

org.hibernate.loader.MultipleBagFetchException:複数のバッグを同時にフェッチできない

これは私のテストケースです:

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;

}

この問題はどうですか?私に何ができる?


編集

さて、私が抱えている問題は、別の「親」エンティティが私の親の内部にあることです。私の実際の動作は次のとおりです。

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はでの2つのコレクションが好きではありませんFetchType.EAGERが、これはバグのようです。私は異常なことをしていません...

問題FetchType.EAGERから削除ParentまたはAnotherParent解決しますが、私はそれが必要なので、@LazyCollection(LazyCollectionOption.FALSE)代わりに使用するのが本当の解決策ですFetchType(解決策はBozhoに感謝します)。


2つの別々のコレクションを同時に取得するために、どのSQLクエリを生成することを望んでいますか?これらを実現できるSQLの種類には、デカルト結合(潜在的に非常に非効率的)または非結合列のUNION(醜い)が必要です。おそらく、SQLでこれをクリーンで効率的な方法で実現できないことが、API設計に影響したと考えられます。
トーマスW

:@ThomasWこれらは、それが生成する必要があり、SQLクエリですselect * from master; select * from child1 where master_id = :master_id; select * from child2 where master_id = :master_id
nurettin

1
あなたが複数ある場合は、simillarエラーを取得することができList<child>fetchTypeために定義された複数の List<clield>
ビッグゼッド

回答:


555

hibernateの新しいバージョン(JPA 2.0をサポート)でこれを処理できると思います。しかし、それ以外の場合は、コレクションフィールドに次のアノテーションを付けることで回避できます。

@LazyCollection(LazyCollectionOption.FALSE)

アノテーションfetchTypeから属性を削除することを忘れないでください@*ToMany

ただし、ほとんどの場合、a Set<Child>はより適切でList<Child>あるため、本当に必要でない限りList-Set

ただし、セットを使用するVlad Mihalceaが彼の回答で説明したように、基になるデカルト積を削除しないことに注意してください。


4
奇妙なことに、それは私のために働いています。をfetchTypeから削除しました@*ToManyか?
Bozho

101
問題は、JPAアノテーションが2つ以上の熱心に読み込まれたコレクションを許可しないように解析されることです。しかし、Hibernate固有の注釈はそれを可能にします。
Bozho

14
複数のEAGERの必要性は完全に現実的です。この制限はJPAの監視だけですか?マルチプルイーガーを使用する際に注意すべき点は何ですか?
AR3Y35 2012年

6
実は、hibernateは1つのクエリで2つのコレクションをフェッチできないのです。したがって、親エンティティを照会する場合、結果ごとに2つの追加の照会が必要になります。これは通常、望ましくないものです。
Bozho

7
これで問題が解決する理由を説明していただければ幸いです。
Webnet 2013

290

Listタイプからタイプに変更するだけSetです。

ただし、Vlad Mihalceaの回答で説明されているように、その下にあるデカルト積を削除しないことに注意してください。


42
リストとセットは同じものではありません。セットは順序を保持しません
Matteo

17
LinkedHashSetが順序を保持する
egallardo '16年

15
これは重要な違いであり、考えてみると完全に正しいです。DBの外部キーによって実装される典型的な多対1は実際にはリストではなく、順序が保持されないためセットです。そのため、Setはより適切です。なぜだかわかりませんが、それが冬眠の違いを生んでいると思います。
fool4jesus

3
私は同じことが複数のバッグを同時にフェッチすることはできませんが、注釈のためではありませんでした。私の場合、私は2つで左結合と分離を行っていました*ToMany。タイプを変更してSet私の問題も解決しました。優れた清楚なソリューション。これが公式の答えになるはずです。
L.ホランダ2014年

20
私は答えが好きでしたが、100万ドルの質問は次のとおりです。なぜですか。なぜセットで例外を表示しないのですか?ありがとう
ひのとり2017年

140

Hibernate固有の@Fetchアノテーションをコードに追加します。

@OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
@Fetch(value = FetchMode.SUBSELECT)
private List<Child> childs;

これにより、HibernateバグHHH-1718に関連する問題が修正されます。


5
@DaveRlz subSelectがこの問題を解決する理由。私はあなたの解決策とその働きを試しましたが、これを使用して問題がどのように解決されたかわかりませんか?
HakunaMatata 2013

これがSet本当に意味がない限り、これが最良の答えです。クエリで結果OneToManyを使用する単一の関係を持つ。クエリで結果を使用する場合。また、受け入れられた回答()で注釈を使用すると、さらに多くのクエリが実行されます。Set1+<# relationships>FetchMode.SUBSELECT1+1LazyCollectionOption.FALSE
mstrthealias 2015

1
FetchType.EAGERはこれに対する適切なソリューションではありません。Hibernate Fetchプロファイルを続行する必要があり、それを解決する必要がある
Milinda Bandara

2
他の2つの回答は私の問題を解決しませんでした。これはしました。ありがとうございました!
Blindworks 2018

3
SUBSELECTが修正する理由を誰かが知っていますが、JOINは修正しませんか?
Innokenty、

42

この質問はStackOverflowとHibernateフォーラムの両方で繰り返し発生するテーマでした。そのため、回答を記事に変えることにしまし

次のエンティティがあると考えます。

ここに画像の説明を入力してください

また、Postすべてのcommentsおよびtagsコレクションとともにいくつかの親エンティティをフェッチする必要があります。

複数のJOIN FETCHディレクティブを使用している場合:

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();

Hibernateは悪名高いものをスローします:

org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags [
  com.vladmihalcea.book.hpjp.hibernate.fetching.Post.comments,
  com.vladmihalcea.book.hpjp.hibernate.fetching.Post.tags
]

Hibernateでは、デカルト積を生成するため、複数のバッグをフェッチすることはできません。

最悪の「解決策」

今、あなたはたくさんの答え、ブログ投稿、ビデオ、またはあなたに SetListコレクションの代わりにように。

それはひどいアドバイスです。やめろ!

Sets代わりに使用Listsをは解決しますMultipleBagFetchExceptionが、デカルト積はそのまま存在します。これは、この「修正」を適用した後、パフォーマンスの問題が判明するため、実際にはさらに悪化します。

適切な解決策

次のトリックを実行できます。

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();

最初のJPQLクエリでdistinctは、SQLステートメントに移動しません。そのため、PASS_DISTINCT_THROUGHJPAクエリヒントをに設定していますfalse

DISTINCTはJPQLで2つの意味を持ち、ここではgetResultList、SQL側ではなくJava側で返されるJavaオブジェクト参照を重複排除する必要があります。チェックアウトこの記事を詳細については。

を使用して最大1つのコレクションをフェッチする限り、問題ありませんJOIN FETCH

複数のクエリを使用することで、最初のコレクション以外の他のコレクションが2次クエリを使用してフェッチされるため、デカルト積を回避できます。

できることは他にもあります

または関連付けのFetchType.EAGERマッピング時に戦略を使用している場合は、簡単に@OneToMany@ManyToManyMultipleBagFetchException

積極的なフェッチは重大なアプリケーションパフォーマンスの問題につながる可能性がある恐ろしいアイデアであるFetchType.EAGERためFetchype.LAZY、からに切り替えた方がよいでしょう。

結論

避けFetchType.EAGERから切り替わらないListSetそうすることにHibernateを行いますという理由だけでは隠しMultipleBagFetchExceptionカーペットの下。一度に1つのコレクションのみを取得すれば、問題ありません。

初期化するコレクションと同じ数のクエリでそれを行う限り、問題ありません。ループでコレクションを初期化しないでください。初期化すると、パフォーマンスが低下するN + 1クエリの問題がトリガーされます。


共有された知識をありがとう。ただし、DISTINCTこのソリューションで はパフォーマンスが大幅に低下します。取り除く方法はありdistinctますか?(Set<...>代わりに戻ってみたが、あまり役に立たなかった)
Leonid Dashko

1
DISTINCTはSQLステートメントに行きません。これPASS_DISTINCT_THROUGHがに設定されてfalseいる理由です。DISTINCTにはJPQLで2つの意味があり、ここでは、SQL側ではなくJava側で重複排除する必要があります。チェックアウトこの記事を詳細については。
Vlad Mihalcea

ヴラド、本当に役に立ったと思ってくれてありがとう。ただし、問題は関連していたhibernate.jdbc.fetch_size(最終的には350に設定した)。偶然にも、ネストされた関係を最適化する方法を知っていますか?例:entity1-> entity2-> entity3.1、entity 3.2(entity3.1 / 3.2は@OneToManyリレーション)
Leonid Dashko

1
@LeonidDashko データのフェッチに関連する多くのヒントについては、私の高性能Java Persistenceブックのフェッチの章を参照してください。
Vlad Mihalcea

1
いいえ、あなたがすることはできません。SQLの観点から考えてみてください。デカルト積を生成せずに複数の1対多の関連付けを結合することはできません。
Vlad Mihalcea

31

この投稿やその他の記事で説明されているすべてのオプションを試した後、修正は次のとおりであるという結論に達しました。

XToManyのすべての場所@XXXToMany(mappedBy="parent", fetch=FetchType.EAGER) とその中間

@Fetch(value = FetchMode.SUBSELECT)

これは私のために働いた


5
追加@Fetch(value = FetchMode.SUBSELECT)は十分でした
user2601995

1
これはHibernateのみのソリューションです。共有JPAライブラリを使用している場合はどうなりますか?
ミシェル

3
あなたがそうするつもりはなかったと私は確信していますが、DaveRlzは3年前に同じことをすでに書いています
phil294

21

それを修正するSetListは、ネストされたオブジェクトの代わりに取ってください。

@OneToMany
Set<Your_object> objectList;

使用することを忘れないでください fetch=FetchType.EAGER

それが動作します。

もう1つのコンセプトがあります CollectionIdリストのみを使用したい場合、Hibernateにます。

ただし、Vlad Mihalceaの回答で説明されているように、その下にあるデカルト積を削除しないことに注意してください。



6

ブースのEAGERリストをJPAに保持し、それらの少なくとも1つにJPAアノテーション@OrderColumnを追加できます(明らかに、注文するフィールドの名前が付いています)。特定の休止状態のアノテーションは必要ありません。ただし、選択したフィールドに0から始まる値がない場合、リストに空の要素が作成される可能性があることに注意してください

 [...]
 @OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
 @OrderColumn(name="orderIndex")
 private List<Child> children;
 [...]

Childrenでは、orderIndexフィールドを追加する必要があります


2

Listの代わりにSetを試しましたが、それは悪夢です。2つの新しいオブジェクト、equals()と hashCode()は両方を区別できません。IDがないためです。

Eclipseのような典型的なツールは、データベーステーブルからそのようなコードを生成します。

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((id == null) ? 0 : id.hashCode());
    return result;
}

この記事を読むこともできますJPA / Hibernateがいかに台無しにされているかを適切に説明しているをます。これを読んだ後、これが私の人生でORMを使用するのはこれが最後だと思います。

私はまた、基本的にORMがひどいものであると言うドメイン駆動設計の人に遭遇しました。


1

saveralコレクションで複雑すぎるオブジェクトがあり、それらすべてをEAGER fetchTypeで持つのは良い考えではない場合、LAZYを使用することをお勧めします。コレクションを本当にロードする必要がある場合はHibernate.initialize(parent.child)、データをフェッチするために使用します。


0

私にとって、問題はEAGERを入れ子にすることでしたフェッチでした。

1つの解決策は、ネストされたフィールドをLAZYに設定し、Hibernate.initialize()を使用してネストされたフィールドをロードすることです。

x = session.get(ClassName.class, id);
Hibernate.initialize(x.getNestedField());

0

私の終わりに、これは次のようにFetchType.EAGERで複数のコレクションがあるときに起こりました:

@ManyToMany(fetch = FetchType.EAGER, targetEntity = className.class)
@JoinColumn(name = "myClass_id")
@JsonView(SerializationView.Summary.class)
private Collection<Model> ModelObjects;

さらに、コレクションは同じ列で結合されていました。

この問題を解決するために、コレクションの1つをFetchType.LAZYに変更しました。

幸運を!〜J


0

両方にコメントするFetchと、LazyCollectionプロジェクトの実行に役立つ場合があります。

@Fetch(FetchMode.JOIN)
@LazyCollection(LazyCollectionOption.FALSE)

0

良い点の1つ@LazyCollection(LazyCollectionOption.FALSE)は、この注釈を持ついくつかのフィールドが共存できることです。FetchType.EAGERような共存が合法である状況でも、できないことです。

たとえば、には(短い)のリストと(短い)のリストがあるOrder場合があります。どちらにも使用せずに両方で使用できますOrderGroupPromotions@LazyCollection(LazyCollectionOption.FALSE)LazyInitializationExceptionMultipleBagFetchException

私の場合@Fetch、私の問題は解決しましたMultipleBacFetchExceptionが、その後LazyInitializationException、悪名高いno Sessionエラーが発生します。


-5

あなたはこれを解決するために新しい注釈を使うことができます:

@XXXToXXX(targetEntity = XXXX.class, fetch = FetchType.LAZY)

実際、フェッチのデフォルト値もFetchType.LAZYです。


5
JPA3.0は存在しません。
holmis83 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.