web-dev-qa-db-ja.com

Hibernateレイジーロードアプリケーションの設計

HibernateSpring フレームワークと組み合わせて使用​​する傾向があり、それは宣言的なトランザクション境界設定機能です(例: @ Transactional )。

よく知られているように、休止状態は可能な限り非侵襲的および透明にしようとしますが、これはもう少し挑戦的ですlazy-loaded関係を使用する場合。


さまざまなレベルの透明度を持つ多くのデザインの選択肢があります。

  1. 遅延ロードされない関係を作成します(例:fetchType=FetchType.EAGER)
    • これは、遅延読み込みの概念全体に反します。
  2. Hibernate.initialize(proxyObj); を使用してコレクションを初期化します
    • これは、DAOへの比較的高い結合を意味します。
    • initializeを使用してインターフェイスを定義できますが、他の実装では同等のものを提供することは保証されません。
  3. 永続的なModelオブジェクト自体にトランザクション動作を追加します( dynamic proxy または@Transactional)を使用します。
    • 動的プロキシアプローチを試したことはありませんが、永続オブジェクト自体で@Transactionalが動作することはなかったようです。おそらく、休止状態が原因で、プロキシを操作することになります。
    • トランザクションが実際に行われているときの制御の喪失
  4. 遅延/非遅延APIの両方を提供します(例:loadData()loadDataWithDeps()
    • アプリケーションに、どのルーチンをいつ使用するかを強制します。これも密結合です
    • メソッドのオーバーフロー、loadDataWithA()、....、loadDataWithX()
  5. byId()操作のみを提供することにより、依存関係の検索を強制します
    • 多くの非オブジェクト指向ルーチン、例えばfindZzzById(zid)が必要で、その後getYyyIds(zid)の代わりにz.getY()が必要です
    • トランザクション間に大きな処理オーバーヘッドがある場合、コレクション内の各オブジェクトを1つずつフェッチすると便利です。
  6. [〜#〜] dao [〜#〜] だけではなく、application @Transactionalの一部を作成します。
    • ネストされたトランザクションの考えられる考慮事項
    • トランザクション管理に適合したルーチンが必要です(例:十分に小さい)
    • 大規模なトランザクションが発生する可能性がありますが、小さなプログラム上の影響
  7. DAOに動的な プロファイルの取得 を指定します(例:loadData(id, fetchProfile);
    • アプリケーションは、使用するプロファイルを知っている必要があります
  8. AoPタイプのトランザクション、たとえば、操作をインターセプトし、必要に応じてトランザクションを実行します
    • バイトコード操作またはプロキシの使用が必要です
    • トランザクションが実行されるときの制御の喪失
    • 黒魔術、いつものように:)

オプションがありませんか?


アプリケーション設計でlazy-loaded関係の影響を最小限にしようとする場合、どのアプローチが望ましいですか?

(ああ、 WoT でごめんなさい)

86
Johan Sjöberg

周知のように、休止状態は可能な限り非侵襲的かつ透過的になるように努めます

最初の仮定は間違っていると思います。アプリケーションは常にエンティティのライフサイクルとロードされるオブジェクトグラフのサイズを処理する必要があるため、Transaparent persistenceは神話です。

Hibernateは思考を読み取ることができないため、特定の操作に特定の依存関係セットが必要であることがわかっている場合は、何らかの方法でHibernateに意図を表現する必要があります。

この観点から、これらの意図を明示的に表現するソリューション(つまり、2、4、および7)は合理的に見え、透明性の欠如に悩まされません。

26
axtavt

どの問題(遅延が原因)が示唆されているのかわかりませんが、私にとって最大の苦痛は、自分のアプリケーションキャッシュでセッションコンテキストが失われないようにすることです。典型的なケース:

  • オブジェクトfooがロードされ、マップに配置されます。
  • 別のスレッドがこのオブジェクトをマップから取得し、foo.getBar()(以前に呼び出されたことがなく、遅延評価されるもの)を呼び出します。
  • ブーム!

したがって、これに対処するために、いくつかのルールがあります。

  • セッションを可能な限り透過的にラップします(たとえば、webappの場合はOpenSessionInViewFilter)。
  • dbセッションバインド/アンバインドが階層のどこかで実行されるスレッド/スレッドプール用の共通APIがあります(try/finally)したがって、サブクラスはそれについて考える必要がありません。
  • スレッド間でオブジェクトを渡す場合、オブジェクト自体の代わりにIDを渡します。受信スレッドは、必要に応じてオブジェクトをロードできます。
  • オブジェクトをキャッシュするときは、オブジェクトではなくIDをキャッシュします。 IDがわかっているときに、2次レベルのHibernateキャッシュからオブジェクトをロードするために、DAOまたはマネージャークラスに抽象メソッドを用意します。 2次レベルのHibernateキャッシュからオブジェクトを取得するコストは、DBにアクセスするよりもはるかに安価です。

ご覧のとおり、これは確かに非侵襲的で透過的に近いところはありません。しかし、熱心なローディングのために私が支払わなければならない価格と比較するために、コストはまだ耐えられます。後者の問題は、エンティティのコレクションはもちろんのこと、単一の参照オブジェクトをロードするときに、バタフライ効果につながる場合があることです。メモリ消費、CPU使用率、そして控えめに言ってもレイテンシーもはるかに悪いので、私はそれに耐えることができると思います。

7
mindas

非常に一般的なパターンは、Webアプリケーションを構築する場合に OpenEntityManagerInViewFilter を使用することです。

サービスを構築している場合、メソッドは複数のエンティティを取得または更新する必要があることが多いため、DAOではなくサービスのパブリックメソッドでTXを開きます。

これにより、「遅延ロード例外」が解決されます。パフォーマンスチューニングのためにより高度なものが必要な場合は、フェッチプロファイルが最適な方法だと思います。

3
Augusto