HibernateのMultipleBagFetchException

おそらく、FetchTypeを使用すあなたの団体のすべてのための怠惰。 これにより、Hibernateは使用時に関連付けを初期化し、必要のないデータを取得するのに時間を費やすことがなくなります。 残念ながら、これは新しい問題を導入します。

残念ながら、これは新しい問題を導入します。 必要に応じて、JOIN FETCH句またはEntityGraphを使用して関連付けを取得する必要があります。 それ以外の場合は、n+1selectの問題が発生し、パフォーマンスの問題が深刻になるか、L A Zyinitializationexceptionが発生します。 複数の関連付けに対してこれを行うと、HibernateはMultipleBagFetchExceptionをスローする可能性があります。この記事では、Hibernateがこの例外をいつスローするかを説明し、それを修正するための2つの最良のオプションを示します。 そのうちの1つは、基数が小さい関連付けに適しており、もう1つは多くの要素を含む関連付けに適しています。 だから、それらの両方を見てみましょう、そしてあなたはあなたのアプリケーションに合ったものを選びます。

MultipleBagFetchExceptionの原因

前回の記事で説明したように、多対多の関連付けのための最も効率的なデータ型については、Hibernateのコレクション型の内部命名はかな あなたのjavaの要素であれば、Hibernateはそれをバッグと呼びます。ユーティルリストは順序付けられていません。 それらが注文されている場合、それはリストと呼ばれます。 だから、あなたのマッピングに応じて、java。ユーティルリストはバッグまたはリストとして扱うことができます。 しかし、実際の生活の中で、これはそれが見えるかもしれないほど混乱していない、心配しないでください。 関連付けの順序を定義するには追加の注釈が必要であり、ほとんどの場合オーバーヘッドです。 そのため、javaを使用する関連マッピングの少なくとも90%を避ける必要があります。ユーティルリストと私は実際のプロジェクトで見てきたことは順不同です。 だから、冬眠はそれらを袋として扱います。

ここでは、Hibernateが本のレビューと著者をバッグとして扱う単純なドメインモデルがあります。JPQLクエリでこれらのバッグの複数を取得しようとすると、デカルト積が作成されます。

TypedQuery<Book> q = em.createQuery("SELECT DISTINCT b "+ "FROM Book b "+ "JOIN FETCH b.authors a "+ "JOIN FETCH b.reviews r "+ "WHERE b.id = 1",Book.class);q.setHint(QueryHints.PASS_DISTINCT_THROUGH, false);List<Book> b = q.getResultList();

これは、パフォーマンスの問題を作成することができます。 Hibernateはまた、複製されるはずの情報とデカルト積のために複製された情報とを区別するのに苦労しています。 そのため、HibernateはMultipleBagFetchExceptionをスローします。P>

java.lang.IllegalArgumentException: org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags: 

MultipleBagFetchExceptionの修正

この例外に関する多くの質問とそれを避けるためのさまざまな解決策を見つけることができます。 しかし、それらの多くは、予期しない副作用が付属しています。 あなたが選択する必要があります間の唯一の2つの修正は、私は、次のセクションで説明しますものです。 どちらが最適かは、クエリが作成するデカルト積のサイズによって異なります。

  1. すべての関連付けに少数の要素しか含まれていない場合、作成されたデカルト積は比較的小さくなります。 このような状況では、関連付けをjavaにマップする属性のタイプを変更できます。ユーティルセット。 Hibernateは、1つのクエリで複数の関連付けをフェッチできます。
  2. 関連の少なくとも1つに多くの要素が含まれている場合、デカルト積は1つのクエリで効率的に取得するには大きすぎます。 次に、必要な結果の異なる部分を取得する複数のクエリを使用する必要があります。

いつものように、アプリケーションのパフォーマンスを最適化するには、異なるトレードオフの間で選択する必要があり、フリーサイズのアプローチはあ 各オプションのパフォーマンスは、デカルト積のサイズと実行しているクエリの数によって異なります。 比較的小さなデカルト積の場合、1つのクエリですべての情報を取得すると、最高のパフォーマンスが得られます。 デカルト積が特定のサイズに達した場合は、複数のクエリに分割することをお勧めします。あなたのアプリケーションに合ったものを選ぶことができるように、私はあなたに両方のオプションを表示します。

それが理由です。

オプション1:リストの代わりにセットを使用する

MultipleBagFetchExceptionを修正する最も簡単な方法は、多対多の関連付けをjavaにマップする属性の型を変更するこユーティルセット。 これはマッピングのわずかな変更であり、ビジネスコードを変更する必要はありません。前に説明したように、前に示したのと同じクエリを実行して、すべての著者とレビューを含む本を取得すると、結果セットにはデカルト積が含まれます。 その製品のサイズは、選択した書籍の数と、関連する著者とレビューの数によって異なります。ここでは、生成されたSQLクエリを見ることができます。

TypedQuery<Book> q = em.createQuery("SELECT DISTINCT b "+ "FROM Book b "+ "JOIN FETCH b.authors a "+ "JOIN FETCH b.reviews r "+ "WHERE b.id = 1",Book.class);q.setHint(QueryHints.PASS_DISTINCT_THROUGH, false);List<Book> b = q.getResultList();

ここでは、生成されたSQLクエリを見ることができます。 要求されたすべての関連付けを取得するには、Hibernateはこれらのエンティティによってマップされたすべての列を選択する必要があります。 3つの内部結合によって作成されたデカルト積と組み合わせると、これはパフォーマンスの問題になる可能性があります。

19:46:20,785 DEBUG - select book0_.id as id1_1_0_, author2_.id as id1_0_1_, reviews3_.id as id1_4_2_, book0_.publisherid as publishe5_1_0_, book0_.publishingDate as publishi2_1_0_, book0_.title as title3_1_0_, book0_.version as version4_1_0_, author2_.firstName as firstNam2_0_1_, author2_.lastName as lastName3_0_1_, author2_.version as version4_0_1_, authors1_.bookId as bookId1_2_0__, authors1_.authorId as authorId2_2_0__, reviews3_.bookid as bookid3_4_2_, reviews3_.comment as comment2_4_2_, reviews3_.bookid as bookid3_4_1__, reviews3_.id as id1_4_1__ from Book book0_ inner join BookAuthor authors1_ on book0_.id=authors1_.bookId inner join Author author2_ on authors1_.authorId=author2_.id inner join Review reviews3_ on book0_.id=reviews3_.bookid where book0_.id=1

このようなクエリを書くたびに、Hibernateは結果セットに製品が含まれていることを隠さないことに注意する必要があります。 このクエリは、各ブックを複数回返します。 同じBookオブジェクトへの参照の数は、著者の数にレビューの数を掛けたもので計算されます。 これを回避するには、SELECT句にDISTINCTキーワードを追加し、クエリヒントhibernateを設定します。クエリ。passDistinctThroughをfalseに設定します。この例では、私のクエリは1冊の本のみを選択し、ほとんどの本は1-3人の著者によって書かれています。 したがって、データベースにこの本のレビューがいくつか含まれていても、デカルト積はまだ比較的小さくなります。

これらの仮定に基づいて、クエリの数を減らすためにデカルト積の非効率性を受け入れる方が速いかもしれません。 これは、膨大な数の本を選択したためにデカルト積が大きくなった場合や、平均的な本が数十人の著者によって書かれている場合に変更される可

オプション2:複数のクエリに分割する

1つのクエリで巨大なデカルト積を取得することは非効率的です。 それはあなたのデータベースに多くのリソースを必要とし、あなたのネットワークに不必要な負荷をかけます。 また、HibernateとJDBCドライバは、クエリ結果を処理するためにより多くのリソースを費やす必要があります。

エンティティの必要なグラフの異なる部分をフェッチする複数のクエリを実行することで、これを回避できます。 この記事の例では、1つのクエリですべての著者を含む書籍を取得し、2番目のクエリですべてのレビューを含む書籍を取得します。 必要なエンティティのグラフがより複雑な場合は、より多くのクエリを使用するか、各エンティティとの関連付けを取得する必要があります。先週の投稿で説明したように、Hibernateは各セッション内に、データベース内の特定のレコードを表すエンティティオブジェクトが1つしかないことを保証し これを使用して、外部キー参照を効率的に解決したり、Hibernateが複数のクエリの結果をマージできるようにしたりできます。

次のログ出力を見ると、両方のクエリによって返されるリストにまったく同じオブジェクトが含まれていることがわかります。 どちらの場合も、Bookオブジェクトは参照@1fを持っています。

Hibernateが2番目のクエリの結果を処理すると、1番目のレベルのキャッシュにそのBookエ 次に、そのオブジェクトを再利用し、返されたレビューをマップされた関連付けに追加しました。

19:52:10,600 DEBUG - select book0_.id as id1_1_0_, author2_.id as id1_0_1_, book0_.publisherid as publishe5_1_0_, book0_.publishingDate as publishi2_1_0_, book0_.title as title3_1_0_, book0_.version as version4_1_0_, author2_.firstName as firstNam2_0_1_, author2_.lastName as lastName3_0_1_, author2_.version as version4_0_1_, authors1_.bookId as bookId1_2_0__, authors1_.authorId as authorId2_2_0__ from Book book0_ inner join BookAuthor authors1_ on book0_.id=authors1_.bookId inner join Author author2_ on authors1_.authorId=author2_.id where book0_.id=119:52:10,633 INFO - 19:52:10,645 DEBUG - select book0_.id as id1_1_0_, reviews1_.id as id1_4_1_, book0_.publisherid as publishe5_1_0_, book0_.publishingDate as publishi2_1_0_, book0_.title as title3_1_0_, book0_.version as version4_1_0_, reviews1_.bookid as bookid3_4_1_, reviews1_.comment as comment2_4_1_, reviews1_.bookid as bookid3_4_0__, reviews1_.id as id1_4_0__ from Book book0_ inner join Review reviews1_ on book0_.id=reviews1_.bookid where book0_.id=119:52:10,648 INFO - 19:52:10,648 INFO - Authors: 219:52:10,648 INFO - Reviews: 2

パフォーマンスに関する考慮事項

複数のクエリを使用してエンティティの必要なグラフを取得する場合、巨大なデカ これにより、関連するすべてのシステムの負荷が軽減され、すべてのクエリで良好なパフォーマンスを確保することが容易になります。

しかし、それは必ずしもこのアプローチがオプション1よりも高速であることを意味するとは限りません。 これで、以前よりも多くのクエリを実行できます。 それらのそれぞれは、データベースの往復を必要とし、実行計画を作成するなど、データベースにいくつかの管理オーバーヘッドを作成します。 そのため、デカルト積のサイズが複数のクエリの実行よりも大きなオーバーヘッドを作成する場合、このオプションはオプション1よりも高速です。

結論

この記事で見てきたように、HibernateのMultipleBagFetchExceptionを2つの方法で解決できます。

  • 関連付けをマップする属性のデータ型を変更し、1つのクエリですべ そのクエリの結果はデカルト積です。 このプロダクトが余りに大きくならない限り、このアプローチは簡単、有効である。
  • 複数のクエリを使用して、エンティティの必要なグラフを取得できます。 これにより、巨大なデカルト積が回避され、膨大な量のデータを取得する必要がある場合にはより良いアプローチになります。

コメントを残す

メールアドレスが公開されることはありません。