Hibernate.orgCommunity Documentation

第13章 Transactions and Concurrency

13.1. session スコープと transaction スコープ
13.1.1. 作業単位(Unit of work)
13.1.2. 長い対話
13.1.3. オブジェクト識別子を考える
13.1.4. 一般的な問題
13.2. データベーストランザクション境界
13.2.1. 管理されていない環境
13.2.2. JTA を使用する
13.2.3. 例外ハンドリング
13.2.4. トランザクションのタイムアウト
13.3. 楽観的同時実行制御
13.3.1. アプリケーションによるバージョンチェック
13.3.2. 拡張セッションと自動バージョニング
13.3.3. デタッチされたオブジェクトと自動バージョニング
13.3.4. 自動バージョニングのカスタマイズ
13.4. 悲観的ロック
13.5. コネクション開放モード

Hibernate と同時実行制御について最も重要な点は、容易に理解できることです。 Hibernate は新たなロックの振る舞いを追加しておらず、直接 JDBC コネクションと JTA リソースを使用します。 JDBC 、 ANSI 、およびデータベース管理システム(DBMS)のトランザクション分離の仕様を少し時間をかけて勉強することを強く推奨します。

Hibernate はメモリ内のオブジェクトをロックしません。アプリケーションは、データベーストランザクションの分離レベルで定義した振る舞いを期待できます。トランザクションスコープのキャッシュでもある Session のお陰で、識別子やクエリにより検索したエンティティはリピータブルリードになります(スカラー値を返すようなレポートクエリは違います)。

バージョニングによる自動的な楽観的同時実行制御に加えて、 SELECT FOR UPDATE 文を使用して、行を悲観的ロックするための(マイナーな) API も提供します。楽観的同時実行制御とこの API については、この章の後のほうで議論します。

データベーストランザクションや長い対話(conversation、ロングトランザクション)だけでなく、 ConfigurationSessionFactory、および Session という粒度で Hibernate が行う同時実行制御の議論を始めます。

SessionFactory は生成することが高価で、スレッドセーフなオブジェクトです。よって、アプリケーションのすべてのスレッドで共有すべきです。通常、アプリケーションの起動時に、 Configuration インスタンスから1度だけ生成します。

Session は高価ではなく、スレッドセーフなオブジェクトでもありません。よって、1つの要求や1つの対話、1つの作業単位(unit of work)に対して1度だけ使い、その後で捨てるべきです。 Session は必要になるまで、 JDBC Connection(もしくは DataSource)を獲得しません。ゆえに、実際に使用するときまでリソースを消費しません。

この状況を完了させるために、データベーストランザクションについても考えなければなりません。データベース内のロックの競合を少なくするために、データベーストランザクションは可能な限り短くするべきです。長いデータベーストランザクションは、アプリケーションの高い並列実行性を阻害します。ゆえに、ユーザーが考えている間(作業単位が完了するまで)データベーストランザクションを開いたままにするのは、たいていの場合よい設計とはいえません。

作業単位というスコープとは何でしょうか?1つの Hibernate Session は、いくつかのデータベーストランザクションをまたがることができるでしょうか?または、スコープと一対一の関係でしょうか?いつ Session を開き、閉じるべきでしょうか?そして、データベーストランザクション境界をどのように分けるのでしょうか?

First, let's define a unit of work. A unit of work is a design pattern described by Martin Fowler as 「 [maintaining] a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems. 」[PoEAA] In other words, its a series of operations we wish to carry out against the database together. Basically, it is a transaction, though fulfilling a unit of work will often span multiple physical database transactions (see 「長い対話」). So really we are talking about a more abstract notion of a transaction. The term "business transaction" is also sometimes used in lieu of unit of work.

1つ目は、 session-per-operation アンチパターンを使ってはいけません。すなわち、1つのスレッドの中で、単純なデータベース呼び出しの度に Session を開いて、閉じてはいけません。もちろん、データベーストランザクションについても同様です。アプリケーション中のデータベース呼び出しは、計画されたシーケンス(planned sequence)を使い、アトミックな作業単位に分類されます。(1つの SQL 文ごとにコミットする自動コミットが、使われないという意味でもあることに注意してください。自動コミットは、 SQL コンソールでアドホックな作業をする際に使うものです。 Hibernate は直ちに自動コミットモードを無効にします。もしくは、アプリケーションサーバーが無効化することを期待します。)データベーストランザクションはオプションではありません。データベースとのすべての通信は、データの読み込みであっても、書き込みであっても、トランザクションの中で行わなければなりません。説明すると、データ読み込みに対して、自動コミットは避けるべきです。なぜなら、多数の小さなトランザクションは、明確に定義された1つの作業単位と比べて、パフォーマンスがよくなることはありません。後者は保守性や拡張性もよりすぐれています。

マルチユーザーのクライアント/サーバーアプリケーションの中で、最もよく使われるパターンは、 session-per-request です。このモデルの中では、クライアントから( Hibernate 永続化層が動作する)サーバーへリクエストが送られ、新しい Hibernate Session が開かれます。そして、この作業単位の中ですべてのデータベース処理が実行されます。作業が完了した(そして、クライアントへのレスポンスが準備できた)時点で、 session をフラッシュし、閉じます。クライアントの要求を処理するために、1つのデータベーストランザクションを使用するでしょう。 Session を開き、閉じる際に、データベーストランザクションを開始し、コミットします。二つの関係は一対一です。このモデルは多くのアプリケーションに完全に適合します。

以降の実装にチャレンジしてください。 Hibernate は単純なこのパターンのために、予め組み込まれた "current session" の管理を提供します。サーバーリクエストを処理する際はトランザクションを開始しなければなりません。そして、レスポンスをクライアントに送信する前にトランザクションを終わらせます。好きな方法で実現できます。一般的な解決策は ServletFilter やサービスメソッドをポイントカットして AOP インターセプター、 proxy/interception コンテナです。 EJB コンテナは EJB セッション Bean をトランザクション境界としてアスペクトをクロスカットする実装の標準的な方法です( CMT による宣言的)。プログラムによるトランザクション境界を使うと決めた場合、簡単に使うため、互換性のあるコードにするために、この章の後のほうにある Hibernate Transaction API のほうがよいです。

Your application code can access a "current session" to process the request by calling sessionFactory.getCurrentSession(). You will always get a Session scoped to the current database transaction. This has to be configured for either resource-local or JTA environments, see 「コンテキスト上のセッション」.

ときどき、「ビューを描画する」まで セッション とデータベーストランザクションのスコープを拡張すると便利なことがあります。これは、要求の処理と描画のフェーズを分けているサーブレットアプリケーションにおいて特に役立ちます。独自のインターセプタを実装すれば、ビューを描画するまでデータベーストランザクションを拡張するのは簡単です。しかし、コンテナ管理トランザクションの EJB に頼る場合は、簡単にはできません。なぜなら、ビューの描画を開始する前に、 EJB のメソッドがリターンした際に、トランザクションが完了するためです。この Open Session in View パターンに関連するヒントと例については、 Hibernate の Web サイトやフォーラムを参照してください。

session-per-request パターンは、作業単位を設計する際に役立つ考えというだけではありません。多くのビジネスプロセスは、ユーザーとの一連の相互作用全体を要求します。その相互作用には、データベースアクセスが含まれます。 Web とエンタープライズアプリケーションでは、データベーストランザクションがユーザーとの相互作用にまで渡ることは許されません。次の例をよく考えてみてください:

この作業単位を(ユーザーの視点で)長期の 対話 (もしくは、アプリケーショントランザクション )と呼びます。アプリケーションにこれを実装する方法はたくさんあります。

最初に思いつく実装は、ユーザーが考えている間、 Session とデータベーストランザクションを開いたままにしておくことです。同時に修正されず、分離と原子性が保証されるように、データベース内のロックは保持したままにします。もちろん、これはアンチパターンです。なぜなら、ロックの競合が発生すると、アプリケーションが同時ユーザー数に応じてスケールアップできなくなるからです。

明らかに、対話を実装するためには、いくつかのデータベーストランザクションを使用するべきです。この場合、ビジネスプロセスの分離を維持することは、アプリケーション層の責務の1つになります。1つの対話は、通常いくつかのデータベーストランザクションに及びます。データベーストランザクションの1つのみ(最後の1つ)が更新したデータを保存し、他はデータを読むだけであれば、それはアトミックです(例えば、いくつかの要求/応答を繰り返すウィザード形式のダイアログ)。これは聞くより、実装したほうが簡単です。 Hibernate の機能を使うのであれば、特に簡単です:

session-per-request-with-detached-objectssession-per-conversation の2つは、利点と欠点を持っています。これについては、この章の後のほうで、楽観的同時実行制御の文脈の中で議論します。

アプリケーションは、2つの異なる Session から同じ永続状態に同時にアクセスできます。しかし、2つの Session インスタンスが永続性クラスの1つのインスタンスを共有することはできません。ゆえに、識別子には2つの異なる概念があるということになります。

特定のSession に追加されたオブジェクトにとって (すなわち、1つの Session のスコープの中では) 、2つの概念は同じです。データベース同一性と JVM 同一性が一致することを、 Hibernate が保証します。しかし、アプリケーションが2つの異なるセッションから「同じ」(永続性識別子の)ビジネスオブジェクトに同時にアクセスする限り、2つのインスタンスは実際に( JVM 識別子が)「異なり」ます。楽観的アプローチによって、 (自動バージョニングの) フラッシュ/コミット時にコンフリクトが解決されます。

このアプローチでは、 Hibernate とデータベースに同時実行についての心配が残ります。一方で、最高のスケーラビリティが提供されます。なぜなら、1スレッドの作業単位の中で一意性が保証されれば、高価なロックや同期化が不要になるためです。 Session ごとに1つのスレッドを貼り付ける限り、アプリケーションはビジネスオブジェクトを synchronize する必要はありません。 Session 内では、アプリケーションはオブジェクトを比較するために、 == を安全に使用できます。

けれども、 Session の外で == を使うアプリケーションは、予期しない結果に遭遇します。これは予期しない場所で起こりえます。例えば、2つの分離インスタンスを同じ Set に put したときなどです。両方とも同じデータベース識別子を持ちます(すなわち、同じ行を表します)。しかし、分離状態のインスタンスの JVM 識別子は当然保証されません。開発者は、永続性クラスの equals()hashCode() メソッドをオーバーライドし、オブジェクト等価性の概念を実装すべきです。警告が1つあります。等価性の実装にデータベース識別子を使わないでください。ユニークな(普通は不変の)属性の組み合わせであるビジネスキーを使ってください。もし、一時オブジェクトが永続化された場合、データベース識別子が変わります。一時オブジェクトを(通常分離インスタンスと共に) Set に保持する場合、ハッシュコードが変わるということは、 Set の契約を破るということです。ビジネスキーのための属性は、データベースの主キーほど安定すべきではないです。オブジェクトが同じ Set の中にいる間だけ、安定を保証すべきです。この問題のより徹底的な議論は、 Hibernate の Web サイトを参照してください。また、これは Hibernate の問題ではなく、単に Java オブジェクトの識別子や等価性をどのように実装すべきかということです。

session-per-user-sessionsession-per-application アンチパターンは使ってはいけません(もちろん、まれに例外があります)。注記:下記の問題のいくつかは、推奨されるパターンとしても出現します。設計を決定する前に、裏の意味を理解するようにしてください。

データベース (もしくはシステム) トランザクションの境界は、常に必要です。データベーストランザクションの外で、データベースとの通信は起きません (これは自動コミットモードに慣れている多くの開発者を混乱させるかもしれません) 。読み込むだけの操作にでも、いつも明確なトランザクション境界を使用してください。分離レベルとデータベースの能力次第で、これは必要ないかもしれませんが、常にトランザクション境界を明示的に指定しても、マイナス面は全くありません。確かに、1つのデータベーストランザクションは多数の小さなトランザクションより (データの読み込みであっても) パフォーマンスがすぐれています。

J2EE 環境に管理されていない状態 (すなわち、スタンドアロン、単純な Web や Swing アプリケーション)でも、管理された状態でも、 Hibernate アプリケーションを実行できます。管理されていない環境では、 Hiberante がデータベースのコネクションプールを提供します。アプリケーション開発者は、トランザクション境界を手動で設定しなければなりません。言い換えると、データベーストランザクションの開始、コミット、ロールバックを開発者自身が設定する必要があるということです。通常、管理された環境では、コンテナ管理によるトランザクション (CMT) が提供されます。例えば、セッション Bean のデプロイメントディスクリプタで宣言的に定義し、トランザクションを組み立てます。プログラムによるトランザクション境界はもう必要ありません。

しかしながら、管理されていないリソースローカルな環境と JTA に依存したシステム (CMT ではなく BMT) の両方に、永続化層をポータブルに保つのは、しばしば望ましいことです。デプロイ環境のネイティブのトランザクションシステムを呼び出す Transaction というラッパー API を Hibernate が提供します。この API を使うかは任意ですが、 CMT のセッション Bean を使わないのであれば、使うことを強く推奨します。

通常、 Session 終了は、4つの異なるフェーズを含みます:

セッションのフラッシュについては、前の方で既に議論しました。管理された環境と管理されていない環境の両方について、トランザクション境界と例外ハンドリングをもっと詳しく見ていきましょう。

Hibernate 永続化層を管理されていない環境で実装する場合は、通常単純なコネクションプール (すなわち DataSource ではない) によって、データベースコネクションを制御します。 Hibernate はそのコネクションプールから必要なコネクションを取得します。セッション/トランザクション制御のイディオムは次のようになります:

// Non-managed environment idiom

Session sess = factory.openSession();
Transaction tx = null;
try {
    tx = sess.beginTransaction();
    // do some work
    ...
    tx.commit();
}
catch (RuntimeException e) {
    if (tx != null) tx.rollback();
    throw e; // or display error message
}
finally {
    sess.close();
}

You do not have to flush() the Session explicitly: the call to commit() automatically triggers the synchronization depending on the FlushMode for the session. A call to close() marks the end of a session. The main implication of close() is that the JDBC connection will be relinquished by the session. This Java code is portable and runs in both non-managed and JTA environments.

より適応性のある解決策は、 Hibernate に予め組み込まれている "current session" コンテキスト管理です。言葉で説明するより下記を見たほうが速いでしょう。

// Non-managed environment idiom with getCurrentSession()

try {
    factory.getCurrentSession().beginTransaction();
    // do some work
    ...
    factory.getCurrentSession().getTransaction().commit();
}
catch (RuntimeException e) {
    factory.getCurrentSession().getTransaction().rollback();
    throw e; // or display error message
}

正規のアプリケーションの中では、このようなコードの切れ端を決して見ないでしょう。致命的な (システム) 例外は、常に「最上位」でキャッチすべきです。言い換えれば、 (永続化層で) Hibernate 呼び出しを実行するコードと、 RuntimeException を制御する (通常はクリーンアップと終了のみ行うことができる) コードは、別々の層の中にあります。 Hibernate によるカレントコンテキスト管理は、この設計をかなり単純にします。必要なのは、 SessionFactory にアクセスすることだけです。例外処理は、この章の後のほうで議論します。

注記:(デフォルトですが) org.hibernate.transaction.JDBCTransactionFactory を選択するべきです。第2の用例としては、 hibernate.current_session_context_class"thread" とするとよいでしょう。

永続化層をアプリケーションサーバー (例えば、 EJB セッション Bean の背後) で実行する場合、 Hibernate から取得するすべてのデータソースコネクションは、自動的にグローバル JTA トランザクションの一部になります。 EJB を使わずに、スタンドアロンの JTA 実装を導入することもできます。 JTA 統合のために、 Hibernate は2つの戦略を提供します。

Bean 管理トランザクション(BMT)を使い、 Transaction API を使う場合、 Hibernate はアプリケーションサーバーに BMT トランザクションの開始と終わりを告げます。すなわち、トランザクション管理のコードは、管理されない環境と同じになります。

// BMT idiom

Session sess = factory.openSession();
Transaction tx = null;
try {
    tx = sess.beginTransaction();
    // do some work
    ...
    tx.commit();
}
catch (RuntimeException e) {
    if (tx != null) tx.rollback();
    throw e; // or display error message
}
finally {
    sess.close();
}

トランザクション境界として Session を使いたい場合、簡単にコンテキストを伝播する機能である getCurrentSession() があるので、 JTAの UserTransaction API を直接使用すべきでしょう。

// BMT idiom with getCurrentSession()

try {
    UserTransaction tx = (UserTransaction)new InitialContext()
                            .lookup("java:comp/UserTransaction");
    tx.begin();
    // Do some work on Session bound to transaction
    factory.getCurrentSession().load(...);
    factory.getCurrentSession().persist(...);
    tx.commit();
}
catch (RuntimeException e) {
    tx.rollback();
    throw e; // or display error message
}

CMT では、トランザクション境界をセッション Bean のデプロイメントディスクリプタで定義し、プログラムでは行いません。ゆえに、コードは次のように少なくなります:

// CMT idiom

 Session sess = factory.getCurrentSession();
 // do some work
 ...

CMT/EJB の中では、常にロールバックが自動的に実施されます。なぜなら、セッション Bean のメソッドにより投げられた制御されていない RuntimeException は、グローバルトランザクションをロールバックするようにコンテナに伝えるためです。 これは、 BMT もしくは CMT と一緒に Hibernate Transaction API を使う必要はまったくないということを意味し、トランザクションにバインドする「現在の」セッションを自動伝搬できます。

Hibernate のトランザクションファクトリを設定する際に、 JTA を直接使う (BMTの) 場合は org.hibernate.transaction.JTATransactionFactory を、 CMT セッション Bean の中では org.hibernate.transaction.CMTTransactionFactory を選択すべきだということに注意してください。 hibernate.transaction.manager_lookup_class をセットすることも思い出してください。なお、 hibernate.current_session_context_class は、セットしないか(後方互換)、 "jta" をセットしてください。

getCurrentSession() オペレーションは、 JTA 環境では1つの欠点を持ちます。デフォルトで使われる after_statement コネクションリリースモードを使用する上で、警告が1つあります。 JTA 仕様の愚かな制約のために、 scroll() または iterate() が返した、閉じられていない ScrollableResults または Iterator インスタンスを Hibernate が自動的にクリーンアップすることはできません。 finally ブロックの中で、 ScrollableResults.close() または Hibernate.close(Iterator) を明示的に呼び出して、裏に潜んだデータベースカーソルを解放 しなければなりません。 (もちろん、多くのアプリケーションでは、 JTA か CMT コードで scroll()iterate() の使用を避けるのは容易です。)

Session が例外 (SQLExceptionを含む) を投げた場合、直ちに、データベーストランザクションをロールバックし、 Session.close() を呼び、 Session インスタンスを破棄すべきです。 Session のいくつかのメソッドは、セッションの状態を 矛盾したまま にします。 Hibernate が投げた例外を、回復できるものとして扱うことはできません。 finally ブロックの中で close() を呼んで、 Session を確実に閉じてください。

HibernateException は、 Hibernate 永続化層の中で発生する多くのエラーをラップする、検査されない例外です ( Hibernate の古いバージョンは違いました) 。私たちの意見は、アプリケーション開発者に回復不可能な例外を下層でキャッチすることを強要すべきではないということです。多くのシステムでは、検査されない例外と致命的な例外は、コールスタックの最初のフレームの1つ (例えば、最上位の層で) でハンドリングし、エラーメッセージをアプリケーションユーザーに表示します (もしくは、他の適切な処理を実施します) 。 Hibernate は、HibernateException 以外の検査されない例外も投げることに注意してください。これらもまた、回復不可能であり、適切な処理を実施すべきです。

Hibernate は、データベースとの対話中に投げられた SQLExceptionJDBCException でラップします。実は、例外をより意味のある JDBCException のサブクラスに変換しようと試みます。元の SQLException は、 JDBCException.getCause() によりいつでも得られます。 Hibernate は、 SessionFactory に追加されている SQLExceptionConverter を使い、 SQLException を適当な JDBCException サブクラスに変換します。デフォルトでは、 SQLExceptionConverter は設定されている SQL 方言により定義されます。一方で、独自の実装に差し替えることもできます (詳細は、 SQLExceptionConverterFactory クラスの Javadoc を参照してください)。標準的な JDBCException のサブタイプを下記に示します。

EJB のような管理された環境が提供するきわめて重要な特徴の1つは、トランザクションのタイムアウトです。これは管理されていないコードには提供できません。トランザクションタイムアウトは、不品行なトランザクションがユーザーにレスポンスを返さないまま、無期限にリソースを使い続けないことを保障します。管理された環境 (JTA) の外では、 Hibernate はこの機能をフルに提供できません。しかしながら、 Hibernate は次のようなデータアクセス操作の制御くらいはできます。データベースレベルのデッドロックや大きなリザルトセットを返すクエリを定義されたタイムアウトによって確実に制限します。管理された環境では、 Hibernate はトランザクションタイムアウトを JTA に委譲します。この機能は、 Hibernate の Transaction オブジェクトによって抽象化されています。



Session sess = factory.openSession();
try {
    //set transaction timeout to 3 seconds
    sess.getTransaction().setTimeout(3);
    sess.getTransaction().begin();
    // do some work
    ...
    sess.getTransaction().commit()
}
catch (RuntimeException e) {
    sess.getTransaction().rollback();
    throw e; // or display error message
}
finally {
    sess.close();
}

CMT Bean の中では setTimeout() を呼び出せないことに注意してください。トランザクションタイムアウトは宣言的に定義されるべきです。

高い並列性と高いスケーラビリティの両方を実現するアプローチは、バージョニングを使った楽観的同時実行制御のみです。更新の衝突を見つけるために(および、更新が失われるのを防ぐために)、バージョン番号もしくはタイムスタンプを使って、バージョンをチェックします。 Hibernate は、楽観的同時実行を行うアプリケーションコードを書くためのアプローチを3つ提供します。私たちが見せるユースケースは、長い対話を持ちますが、バージョンチェックはまだ1つのデータベーストランザクションの中で更新を失うことを防ぐ利点も持っています。

Hibernate にほとんど助けてもらわずに実装するケースです。データベースとのやり取りは、それぞれ新しい Session の中で起こります。開発者は、すべての永続性インスタンスを操作する前に、データベースから再読み込みする責務があります。このアプローチでは、対話トランザクションの分離を守るために、アプリケーション自身がバージョンチェックを行う必要があります。このアプローチは、データベースアクセスの中では、最も非効率です。エンティティ EJB と最も似ているアプローチです。

// foo is an instance loaded by a previous Session

session = factory.openSession();
Transaction t = session.beginTransaction();
int oldVersion = foo.getVersion();
session.load( foo, foo.getKey() ); // load the current state
if ( oldVersion != foo.getVersion() ) throw new StaleObjectStateException();
foo.setProperty("bar");
t.commit();
session.close();

<version> を使って、 version プロパティをマッピングします。 Hibernate は、エンティティがダーティである場合、フラッシュし、その間に version プロパティを自動的にインクリメントします。

もちろん、データの並列性が低い環境で運用しており、バージョンチェックが不要なら、このアプローチを使い、バージョンチェックをスキップするだけです。その場合は、長い対話には、 最後にコミットしたものが勝つ がデフォルトの戦略でしょう。このアプローチは、アプリケーションのユーザーを混乱させるかもしれないことを心に留めて置いてください。それは、エラーメッセージや競合した変更をマージする機会がないまま、更新を失う可能性があるからです。

確かに、マニュアルによるバージョンチェックは、些細な儀式だけで実行できますが、多くのアプリケーションにとって実用的ではありません。しばしば、1つのインスタンスだけでなく、修正されたオブジェクトの完全なグラフをチェックしなければなりません。 Hibernate は、設計パラダイムとして、拡張 Session か分離されたインスタンスを自動的にバージョンチェックします。

1つの Session インスタンスとその永続性インスタンスは、 session-per-conversation として知られる、対話全体で使われます。 Hibernate はフラッシュする際に、インスタンスのバージョンをチェックします。同時に修正されたことを検出すると、例外を投げます。この例外をキャッチして扱うのは、開発者の責任です (一般的な選択肢は、変更をマージするか古くないデータでビジネス対話を再スタートする機会をユーザーに提供することです)。

ユーザーの対話を待っているときは、 Session を基礎となる JDBC コネクションから切り離します。このアプローチは、データベースアクセスの中では、最も効率的です。アプリケーションは、バージョンチェックや分離されたインスタンスを再追加することに関心を持つ必要はありません。また、あらゆるデータベーストランザクションの中でインスタンスを再読み込みする必要はありません。

// foo is an instance loaded earlier by the old session

Transaction t = session.beginTransaction(); // Obtain a new JDBC connection, start transaction
foo.setProperty("bar");
session.flush();    // Only for last transaction in conversation
t.commit();         // Also return JDBC connection
session.close();    // Only for last transaction in conversation

foo オブジェクトは、自分をロードした Session をまだ知っています。古いセッションの上で新しいデータベーストランザクションを開始することで、新しいコネクションを取得し、そのセッションが再開されます。データベーストランザクションをコミットすることで、セッションから JDBC コネクションを切断し、コネクションをプールに返します。再接続した後、更新していないデータのバージョンチェックを強制するために、他のトランザクションにより更新されているかもしれないオブジェクトに関して、 LockMode.READ をつけて Session.lock() を呼び出すことができます。更新して いる データをロックする必要はありません。通常、拡張 SessionFlushMode.MANUAL をセットします。最後のデータベーストランザクションの周期でのみ、対話の中で変更されたすべてを実際に永続化させるためです。ゆえに、最後のデータベーストランザクションのみ flush() オペレーションを含みます。そして、対話を終わらせるために、セッションも close() します。

ユーザーが考慮中に、格納することができないくらい Session が大きいのであれば、このパターンは問題があります。例えば、 HttpSession は可能な限り小さく保つべきです。 Session は (強制的に) 1次キャッシュでもあり、ロードしたオブジェクトをすべて保持します。おそらく、リクエスト/レスポンスのサイクルが数回であれば、この戦略が使えます。1つの対話のためだけに Session を使うべきです。なぜなら、すぐに新鮮でないデータを持つためです。

切断した Session を永続化層の近くで保持すべきであることに注意してください。言い換えると、3層環境の中で Session を保持するために、 EJB ステートフルセッション Bean を使ってください。 HttpSession に格納するために、 Web 層に転送しないでください (別の層へのシリアライズもしないでください)。

拡張セッションパターン (もしくは、 session-per-conversation ) は、自動的なカレントセッションコンテキスト管理を実施するより難しい。このために、あなたは CurrentSessionContext の実装を供給する必要があります。 Hibernate Wiki にある例を参照してください。

マッピングの optimistic-lock 属性に false を設定することにより、特定のプロパティやコレクションのために自動バージョンインクリメントを無効にできます。プロパティがダーティであっても、バージョンをインクリメントしません。

レガシーのデータベーススキーマは、しばしば固定的であり、変更できません。または、他のアプリケーションが同じデータベースにアクセスしなければならず、そのアプリケーションはバージョン番号やタイムスタンプさえ操作する方法を知りません。どちらの場合も、テーブルの特定のカラムを当てにして、バージョニングを行えません。バージョンやタイムスタンプのプロパティをマッピングせずに、バージョンチェックさせるために、 <class> マッピングに optimistic-lock="all" を指定してください。行のすべてのフィールドの状態を比較するようになります。これは、 Hibernate が古い状態と新しい状態を比較できる場合に、理論的に動作するだけであることに注意してください。例えば、 session-per-request-with-detached-objects ではなく、1つの長い Session を使う場合です。

ときどき、行われた変更が重ならない限り、同時に行われた変更を受け入れることができます。 <class> マッピングに optimistic-lock="dirty" を設定した場合、フラッシュする際に、 Hibernate はダーティフィールドのみを比較します。

専用のバージョン/タイムスタンプのカラムを使う場合、もしくはすべて/ダーティのフィールドを比較する場合どちらであっても、 Hibernate はエンティティごとに1つの UPDATE 文を (適切な WHERE 節と共に) 使い、バージョンチェックと情報の更新を行います。関連するエンティティの再追加をカスケードするために、連鎖的な永続化を使用した場合、不必要な更新を実行するかもしれません。これは通常問題になりません。しかし、分離したインスタンスを変更していなくとも、データベースの on update トリガーが実行されるかもしれません。 <class> マッピングに select-before-update="true" を設定することによって、この振る舞いをカスタマイズできます。確実に変更されたかを確認するために、行を更新する前に、必ずインスタンスを SELECT します。

ユーザーがロック戦略に悩むのに多くの時間を費やすことを意図していません。通常は、 JDBC コネクションに分離レベルを指定し、単にデータベースにすべての仕事をさせれば十分です。しかしながら、高度なユーザーは、排他的な悲観的ロックを獲得することか、新しいトランザクションが開始される際にロックを再獲得することをときどき望むかもしれません。

Hibernate はいつもデータベースのロックの仕組みを使います。メモリ内のオブジェクトを決してロックしません。

LockMode クラスは、 Hibernate が獲得できる異なるロックレベルを定義します。以下の仕組みにより、ロックを獲得できます。

「明示的なユーザー要求」とは、下記の方法の1つで言い表せます。

UPGRADE もしくは UPGRADE_NOWAIT が指定された Session.load() が呼び出され、かつ要求されたオブジェクトがセッションによってまだロードされていなかった場合は、 SELECT ... FOR UPDATE を使って、オブジェクトがロードされます。 load() で呼び出されたオブジェクトが、要求されているより制限が少ないロックですでにロードされていた場合は、 Hibernate はそのオブジェクトのために、 lock() を呼び出します。

指定されたロックモードが READ もしくは、 UPGRADEUPGRADE_NOWAIT だった場合、 Session.lock() は、バージョン番号のチェックを実施します。 (UPGRADE もしくは UPGRADE_NOWAIT の場合、 SELECT ... FOR UPDATE が使われます。)

データベースが要求されたロックモードをサポートしていない場合、 Hibernate は(例外を投げる代わりに、)適切な代わりのモードを使います。これは、アプリケーションがポータブルであることを保証します。

Hibernate のレガシー(2.x)の JDBC コネクション管理に関する振る舞いは、最初に必要とした際に Session がコネクションを得るというものでした。そして、セッションが閉じられるまで、そのコネクションを保持しました。 Hibernate 3.x は、セッションに JDBC コネクションをどのように制御するかを伝えるコネクション開放モードという概念を導入しました。以降の議論は、構成された ConnectionProvider を通して提供されるコネクションに適切であることに注意してください。異なる開放モードは、 org.hibernate.ConnectionReleaseMode に列挙された値により確認されます。

コンフィグレーションパラメータの hibernate.connection.release_mode は、使用する開放モードを指定するために使います。指定できる値は次の通りです: