web-dev-qa-db-ja.com

大規模トランザクションの途中でHibernateセッションを安全にクリアする

文字通り何十万ものアイテムの作成と更新を必要とする操作にSpring + Hibernateを使用しています。このようなもの:

_{
   ...
   Foo foo = fooDAO.get(...);
   for (int i=0; i<500000; i++) {
      Bar bar = barDAO.load(i);
      if (bar.needsModification() && foo.foo()) {
         bar.setWhatever("new whatever");
         barDAO.update(bar);
         // commit here
         Baz baz = new Baz();
         bazDAO.create(baz);
         // if (i % 100 == 0), clear
      }
   }
}
_

途中で変更が失われないように自分を保護するため、barDAO.update(bar)の直後に変更をコミットします。

_HibernateTransactionManager transactionManager = ...; // injected by Spring
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus transactionStatus = transactionManager.getTransaction(def);
transactionManager.commit(transactionStatus);
_

この時点で、_org.springframework.orm.hibernate3.support.ExtendedOpenSessionInViewFilter_にラップされたトランザクションでプロセス全体が実行されていると言う必要があります(はい、これはWebアプリケーションです)。

これはすべて1つの例外を除いて正常に機能します。数千回の更新/コミットの後、プロセス全体が非常に遅くなります。これは、Spring/Hibernateによって保持されているオブジェクトの増加によりメモリが肥大化していることが原因と考えられます。

Hibernateのみの環境では、これはorg.hibernate.Session#clear()を呼び出すことで簡単に解決できます。

さて、質問:

  • clear()に適したタイミングはいつですか?パフォーマンスコストは大きくなりますか?
  • barbazのようなオブジェクトが自動的にリリース/ GCdされないのはなぜですか?コミット後もセッションにそれらを保持する意味は何ですか(とにかく到達できない反復の次のループでは)。私はこれを証明するためにメモリダンプを行っていませんが、完全に終了するまでメモリが残っているというのが私の好感です。これに対する答えが「休止状態のキャッシュ」である場合、使用可能なメモリが少なくなるとキャッシュがフラッシュされないのはなぜですか?
  • org.hibernate.Session#clear()を直接呼び出すことは安全/推奨されますか?(Springコンテキスト全体、遅延読み込みなどのことを念頭に置いて)?同じことを達成するために使用可能なSpringラッパー/対応物はありますか?
  • 上記の質問に対する答えが真である場合、clear()がループ内で呼び出されると仮定して、オブジェクトfooはどうなりますか? foo.foo()が遅延読み込みメソッドの場合はどうなりますか?

回答ありがとうございます。

31
mindas

Clear()はいつ良いタイミングですか?パフォーマンスコストは大きくなりますか?

定期的に、理想的には変更をフラッシュした後のJDBCバッチサイズと同じです。ドキュメンテーションは バッチ処理 に関する章の一般的なイディオムについて説明しています:

13.1。バッチ挿入

新しいオブジェクトを永続的にする場合は、1次キャッシュのサイズを制御するために、セッションを定期的にflush()してからclear()します。

_Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

for ( int i=0; i<100000; i++ ) {
    Customer customer = new Customer(.....);
    session.save(customer);
    if ( i % 20 == 0 ) { //20, same as the JDBC batch size
        //flush a batch of inserts and release memory:
        session.flush();
        session.clear();
    }
}

tx.commit();
session.close();
_

そして、これはパフォーマンスcostを持つべきではありません、au contraire:

  • 汚れを追跡するために追跡するオブジェクトの数を低く保つことができます(フラッシュは高速でなければなりません)。
  • メモリを再利用できるようにする必要があります。

Barやbazなどのオブジェクトが自動的にリリース/ GCdされないのはなぜですか?コミット後もセッションにそれらを保持する意味は何ですか(とにかく到達できない反復の次のループでは)。

エンティティを追跡したくない場合は、セッションを明示的にclear()する必要があります。それだけで、それが機能します(エンティティを「失うことなく」トランザクションをコミットしたい場合があります)。

しかし、私が見ることができることから、barとbazのインスタンスは、クリア後にGCの候補になるはずです。正確に何が起こっているかを確認するためにメモリダンプを分析することは興味深いでしょう。

org.hibernate.Session#clear()を直接呼び出すことは安全/推奨されますか

あなたがそれらを失うことがないように保留中の変更をflush()している限り(これがあなたが望むものでない限り)、私はそれに関する問題を見ません(あなたの現在のコードは100ループごとに作成を失うでしょうが、おそらくそれはいくつかの疑似コード)。

上記の質問への答えがtrueの場合、clear()がループ内で呼び出されたとすると、オブジェクトfooはどうなりますか? foo.foo()が遅延ロードメソッドの場合はどうなりますか?

clear() を呼び出すと、読み込まれたすべてのインスタンスが Session から削除され、エンティティが切り離されます。後続の呼び出しでエンティティを「アタッチ」する必要がある場合、失敗します。

46
Pascal Thivent

セッションをクリアした後、セッションにあったオブジェクトを引き続き使用したい場合は、続行するためにそれらをSession.refresh(obj)する必要があることを指摘したいと思います。

そうしないと、次のエラーが発生します。

org.hibernate.NonUniqueObjectException
1
smdb21