web-dev-qa-db-ja.com

Hibernate Open Session in Viewが悪いプラクティスと見なされるのはなぜですか?

そして、LazyLoadExceptionsを回避するためにどのような代替戦略を使用しますか?

ビューのオープンセッションには次の問題があることを理解しています。

  • 異なるjvmで実行されているレイヤードアプリケーション
  • トランザクションは最後にのみコミットされるため、おそらく結果を以前に取得したいでしょう。

しかし、アプリケーションが単一のvmで実行されていることがわかっている場合は、ビューセッションでオープンセッションを使用することで苦痛を和らげてみませんか?

102
HeDinges

初期化されていない可能性のあるプロキシ、特にコレクションをビューレイヤーに送信し、そこから休止状態の読み込みをトリガーすると、パフォーマンスと理解の両方の観点から問題が発生する可能性があるためです。

理解

OSIVを使用すると、データアクセスレイヤーに関連する懸念があるビューレイヤーが「汚染」されます。

ビューレイヤーは、遅延読み込み時に発生する可能性のあるHibernateExceptionを処理する準備ができていませんが、おそらくデータアクセスレイヤーがそうです。

パフォーマンス

OSIVは、カーペットの下に適切なエンティティの読み込みを行う傾向があります。コレクションまたはエンティティが遅延初期化されていることに気付かない傾向があります(おそらくN + 1)。より便利に、より少ないコントロール。


更新:このテーマに関するより大きな議論については The OpenSessionInViewアンチパターン を参照してください。著者は3つの重要なポイントをリストしています:

  1. 遅延初期化ごとにクエリが取得されるため、各エンティティにはN + 1個のクエリが必要になります。Nは遅延アソシエーションの数です。画面に表形式のデータが表示されている場合、Hibernateのログを読むことは、あなたがするべきではないことの大きなヒントです
  2. これは、プレゼンテーションレイヤーでDBを使用して爪を汚すため、レイヤードアーキテクチャを完全に無効にします。これは概念的な短所なので、私はそれに耐えることができますが、当然の結果があります
  3. 最後になりますが、セッションの取得中に例外が発生すると、ページの書き込み中に例外が発生します。ユーザーにクリーンなエラーページを提示することはできず、できることはエラーメッセージを本文に書き込むことだけです
45
Robert Munteanu

より長い説明については、私の Anti-Patternでセッションを開く の記事をご覧ください。それ以外の場合は、ビューでオープンセッションを使用しない理由の概要を以下に示します。

Open Session In Viewは、データを取得するのに悪いアプローチを取ります。ビジネスレイヤーに、ビューレイヤーに必要なすべての関連付けを取得する方法を決定させる代わりに、ビューレイヤーがプロキシの初期化をトリガーできるように、永続コンテキストを開いたままにします。

enter image description here

  • OpenSessionInViewFilterは、基になるopenSessionSessionFactoryメソッドを呼び出し、新しいSessionを取得します。
  • SessionTransactionSynchronizationManager にバインドされます。
  • OpenSessionInViewFilterjavax.servlet.FilterChainオブジェクト参照のdoFilterを呼び出し、リクエストはさらに処理されます
  • DispatcherServlet が呼び出され、HTTP要求を基になるPostControllerにルーティングします。
  • PostControllerPostServiceを呼び出して、Postエンティティのリストを取得します。
  • PostServiceは新しいトランザクションを開き、HibernateTransactionManagerSessionによって開かれたのと同じOpenSessionInViewFilterを再利用します。
  • PostDAOは、遅延関連付けを初期化せずにPostエンティティのリストを取得します。
  • PostServiceは基になるトランザクションをコミットしますが、Sessionは外部で開かれたため閉じられません。
  • DispatcherServletはUIのレンダリングを開始し、UIがレイジーアソシエーションをナビゲートし、初期化をトリガーします。
  • OpenSessionInViewFilterSessionを閉じることができ、基になるデータベース接続も解放されます。

一見すると、これはひどいことではないように見えますが、データベースの観点から見ると、一連の欠陥がより明白になり始めています。

サービス層はデータベーストランザクションを開いたり閉じたりしますが、その後、明示的なトランザクションは行われません。このため、UIレンダリングフェーズから発行されるすべての追加ステートメントは、自動コミットモードで実行されます。各ステートメントはトランザクションログをディスクにフラッシュする必要があるため、自動コミットによりデータベースサーバーに負荷がかかり、データベース側で大量のI/Oトラフィックが発生します。最適化の1つは、Connectionを読み取り専用としてマークすることです。これにより、データベースサーバーはトランザクションログへの書き込みを回避できます。

ステートメントはサービスレイヤーとUIレンダリングプロセスの両方によって生成されるため、懸念の分離はもうありません。 生成されるステートメントの数をアサートする の統合テストを作成するには、アプリケーションをWebコンテナーにデプロイしながら、すべてのレイヤー(Web、サービス、DAO)を通過する必要があります。インメモリデータベース(HSQLDBなど)および軽量Webサーバー(Jettyなど)を使用する場合でも、これらの統合テストは、レイヤーが分離され、バックエンド統合テストがデータベースを使用する場合よりも実行が遅くなります。フロントエンドの統合テストでは、サービスレイヤーを完全に模倣していました。

UIレイヤーは、関連付けのナビゲートに限定され、N + 1クエリの問題を引き起こす可能性があります。 Hibernateはバッチで関連付けを取得するために @BatchSize を提供し、このシナリオに対処するために FetchMode.SUBSELECT を提供しますが、注釈はデフォルトの取得プランに影響を与えます。すべてのビジネスユースケースに適用されます。このため、データアクセスレイヤークエリは、現在のユースケースデータフェッチ要件に合わせて調整できるため、はるかに適しています。

最後になりましたが、データベース接続はUIレンダリングフェーズ(接続リリースモードによって異なります)を通して保持される可能性があります。これにより、接続リース時間が長くなり、データベース接続プールの輻輳による全体的なトランザクションスループットが制限されます。接続が保持されるほど、プールから接続を取得するために他の同時リクエストが待機します。

そのため、接続の保持が長すぎるか、単一のHTTP要求に対して複数の接続を取得/解放するため、基礎となる接続プールに圧力がかかり、スケーラビリティが制限されます。

春のブーツ

残念ながら、 Spring Bootではビューでセッションを開くはデフォルトで有効になっています

そのため、application.properties構成ファイルに次のエントリがあることを確認してください。

spring.jpa.open-in-view=false

これによりOSIVが無効になるため、 LazyInitializationExceptionを適切に処理 できます。

39
Vlad Mihalcea
  • トランザクションはサービス層でコミットできます-トランザクションはOSIVに関連していません。トランザクションではなく、開いたままのSessionが実行中です。

  • アプリケーション層が複数のマシンに分散している場合、ほとんどcant OSIVを使用します-必要なものをすべて初期化してから、オブジェクトをネットワーク経由で送信します。

  • OSIVは、遅延読み込みのパフォーマンス上の利点を活用するための、素晴らしく透明な(つまり、コードがそれを認識していない)方法です。

24
Bozho

Open Session In Viewが悪い習慣だとは思わないでしょう。その印象を与えるものは何ですか?

Open-Session-In-Viewは、Hibernateでセッションを処理するためのシンプルなアプローチです。それは単純であるため、時には単純化されます。リクエストに複数のトランザクションを含めるなど、トランザクションをきめ細かく制御する必要がある場合、Open-Session-In-Viewは常に適切なアプローチではありません。

他の人が指摘しているように、OSIVにはいくつかのトレードオフがあります。キックオフしているトランザクションを認識する可能性が低いため、N + 1の問題が発生しやすくなります。同時に、ビューの小さな変更に対応するためにサービスレイヤーを変更する必要がないことを意味します。

13

SpringなどのInversion of Control(IoC)コンテナーを使用している場合は、 bean scoping を参照してください。基本的に、Springに、ライフサイクルがリクエスト全体にまたがるHibernate Sessionオブジェクトを提供するように指示しています(つまり、HTTPリクエストの開始および終了時に作成および破棄されます)。 IoCコンテナが私のためにそれを管理するので、LazyLoadExceptionsを心配したり、セッションを閉じる必要はありません。

前述のように、N + 1 SELECTのパフォーマンスの問題について考える必要があります。パフォーマンスが問題となる場所で積極的な結合ロードを行うために、後でHibernateエンティティをいつでも構成できます。

Beanスコーピングソリューションは、Spring固有のものではありません。 PicoContainerが同じ機能を提供し、他の成熟したIoCコンテナーが同様の機能を提供していることを確信しています。

5
0sumgain

私自身の経験では、OSIVはそれほど悪くはありません。私が行った唯一の取り決めは、2つの異なるトランザクションを使用することです。1つ目は「サービスレイヤー」で開き、「ビジネスロジック」があります-2つ目はビューのレンダリングの直前に開きます

4
Davide

私はブログで公開セッションをいつ使用するかについて、いくつかのガイドラインを投稿しました。興味があればチェックしてください。

http://heapdump.wordpress.com/2010/04/04/should-i-use-open-session-in-view/

3
Chris Upton

これはあまり役に立ちませんが、ここで私のトピックを確認できます:* OpenSessionInViewを使用したHibernate Cache1 OutOfMemory

OpenSessionInViewと多くのエンティティがHibernateキャッシュレベル1に残り、ガベージコレクションされないため、OutOfMemoryの問題があります(ページごとに500アイテムの多くのエンティティを読み込みますが、すべてのエンティティはキャッシュに残ります)

1

私はHibernateに対してさびついています。したがって、トランザクションの境界は、セッションの開始/停止イベントと同じである必要はありません。

OSIVのimoは、リクエストがDBアクセスを行う必要があるたびに「永続コンテキスト」(別名セッション)を開始するためのコードを書くことを避けることができるため、主に役立ちます。

サービス層では、おそらく、「Required、New Requiredなど」など、さまざまなトランザクションのニーズを持つメソッドを呼び出す必要があります。これらのメソッドに必要なのは、誰か(つまり、OSIVフィルター)が永続コンテキストを起動したことだけです。したがって、心配する必要があるのは、「このスレッドの休止セッションを教えてください。 DBスタッフ」。

1
rjk2008