web-dev-qa-db-ja.com

Hibernateが更新/挿入/削除の順序を決定する方法

最初にHibernateについて忘れましょう。 2つのテーブルAとBがあるとします。2つのトランザクションがこれら2つのテーブルの同じレコードを更新していますが、txn 1はBを更新してからAを更新し、txn 2はAを更新してからBを更新します。これは典型的なデッドロックの例です。これを回避する最も一般的な方法は、リソースを取得する順序を事前に定義することです。たとえば、テーブルAを更新してからBを更新する必要があります。

Hibernateに戻ります。 1つのセッションで多数のエンティティを更新しているときに、セッションをフラッシュすると、さまざまなエンティティの変更により、対応する挿入/更新/削除ステートメントがDBに生成されます。 Hibernateには、エンティティ間の更新の順序を決定するアルゴリズムがありますか?そうでない場合、最初の段落で説明されているデッドロック状態を防止するためにHibernateがどのように使用されましたか?

Hibernateが順序を維持している場合、どのようにして順序を確認または制御できますか? DBでの明示的な更新がHibernateと競合したくないので、デッドロックが発生します。

33
Adrian Shum

あなたが説明する問題はデータベースでは処理されず、私の経験では、Hibernateでも完全には処理されません。

それが問題にならないように、明示的な手順を実行する必要があります。

Hibernateがいくつかの作業を行います。以前の回答と同様に、Hibernateは、分離されたフラッシュ内で、挿入、削除、更新が確実に達成可能な順序で適用されるように順序付けされます。 AbstractFlushingEventListenerクラスの performExecutions(EventSource session) を参照してください。

すべてのSQL(および2次キャッシュ更新)を特別な順序で実行して、外部キー制約に違反しないようにします。

  1. 挿入された順序での挿入
  2. アップデート
  3. コレクション要素の削除
  4. コレクション要素の挿入
  5. 実行された順序での削除

一意の制約がある場合、特に1対多の子を置き換える(古いものを削除/新しいものを挿入する)が、古い子と新しい子の両方が同じ一意の制約(同じメールアドレスなど)を共有する場合は、この順序を知ることが非常に重要です。 )。この場合、削除/挿入する代わりに古いエントリを更新するか、削除後にのみフラッシュしてから挿入を続行することができます。より詳細な例については、 この記事 を確認できます。

更新の順序は指定しないことに注意してください。 Hibernateコードを調べると、更新順序はエンティティが永続化コンテキストに追加された順序に依存すると思います[〜#〜]ではありません[〜#〜]更新された順序。それはあなたのコードで予測できるかもしれませんが、Hibernateコードを読んでも、私がその順序に依存するだろうと感じました。

私が考えることができる3つの解決策があります:

  1. hibernate.order_updatestrueに設定してみてください。これは、同じテーブルの複数の行が更新されているときにデッドロックを回避するのに役立ちますが、複数のテーブルにわたるデッドロックには役立ちません。
  2. 更新を行う前に、トランザクションが PESSIMISTIC_WRITE のいずれかのエンティティをロックするようにします。使用するエンティティは特定の状況に依存しますが、デッドロックのリスクがある場合にエンティティが一貫して選択されていることを確認する限り、ロックが取得できるまでトランザクションの残りをブロックします。
  3. デッドロックが発生したときにキャッチして適切な方法で再試行するようにコードを記述します。デッドロック再試行を管理するコンポーネントは、現在のトランザクション境界の外側に配置する必要があります。これは、失敗したセッションを閉じて、関連するトランザクションをロールバックする必要があるためです。 この記事 に、自動再試行AOPアスペクトの例があります。
37
David Edwards