Hibernate は O/R マッピングツールと呼ばれる、リレーショナルデータベースとオブジェクトモデルとの間を埋めるフレームワークです。

O/Rマッピングを行うとトランザクション制御をあまり考えなくてもいいような気になってしまう感があります。でも、トランザクション制御って非常に大切な要素です。しかも、O/Rマッピングを行っていようと、トランザクションのことを何も考えていなければ、データの不整合が起きる可能性もあればデッドロックが起きる可能性もあります。

JDBCを直接扱う場合は、まだトランザクションを気にしながらコーディングする方なのでいいのですが、O/Rマッピングを使う場合に忘れがちなトランザクションのことを、ちょっとでも頭にとどめておくためにメモ程度に記録しておきます。(間違いを見つけたらコメントください) + Hibernate Reference 3.0

[参考]

トランザクション処理〈上〉-概念と技法

トランザクション処理〈下〉-概念と技法

Hibernateのトランザクション制御方法

SessionFactory と Session

SessionFactory クラスと Session クラスが、Hibernate の肝となる部分です。SessionFactoryはスレッドセーフで、アプリケーションで一つのインスタンスのみを生成すればいいです。ただし、データベースとSessionFactoryが1対1で対応するので、アプリケーションで複数のデータベースにアクセスする場合(グローバルトランザクション)はその分だけ SessionFactory を生成します。

SessionFactory は データベースの数だけ生成する

次に、Sessionです。Sessionは、スレッドセーフではありません。マルチスレッド環境下でSessionオブジェクトを使用する場合は気をつける必要があります。Webアプリケーションは基本的にマルチスレッド環境です。したがって、Sessionは使い捨てで使用するべきです。

Hibernateはトランザクション管理はベースとなるデータコネクションに任せています。データコネクションがJTAに管理されていれば、Sessionが実行する処理はトランザクションの一部となります。

Sessionのフラッシュ

基本的にSessionはコネクションと思えばいいようです。Sessionの処理手順としては基本的に下のような感じになります。

1. セッションのオープン
例) sessionFactory.openSession()
↓
2. トランザクション開始
例) transaction = session.beginTransaction
↓
3. トランザクション
例) session.save()
session.update()
session.delete()
session.saveOrUpdate()
↓
4. データベースと同期
例) session.flush()
↓
5. トランザクションコミット or ロールバック
例) transaction.commit()
transaction.rollback()
↓
6. セッションのクローズ
例) session.close()

Sessionの flush は大事な概念のようです。Hibernateは永続化データをキャッシュします。実際には、データベースにはリアルタイムに処理が施されているわけで、いざ save するときにはデータベースのデータと同期が取れていない可能性があります。

これを防止するのが flush の役割です。Hibernateはいろいろな処理を行う際に flush を自動で呼び出してくれます。例えば transaction.commit() の直前などです。しかし、明示的に session.flush() を呼び出すことはよい手法となっています。

HibernateExceptionは致命的な例外

HibernateでスローされるHibernateExceptionはすべて致命的な例外ととらえます。これは、現在のSessionが整合性の取れないものになっている可能性があることを示しています。つまり、HibernateExceptionがスローされたら、トランザクションをロールバックして、session.close() を即座に呼び出す必要があるということです。

Session session = factory.openSession(); 
Transaction tx = null; 
 
try { 
   tx = session.beginTransaction(); 
   // 何らかの処理 
    tx.commit(); 
} 
catch (Exception e) { 
    if (tx != null) { 
        tx.rollback(); 
    } 
    throw e; 
} 
finally { 
    session.close(); 
} 

楽観的同時平行性制御 (Optimisticトランザクション)

通常、データベーストランザクションがユーザとの対話に関与することは許されません。ユーザとの対話に関与(つまり、Webアプリケーションで画面をまたいでトランザクションをかけ続けること)はロングトランザクションとなり、他のデータベーストランザクションがリソースにアクセスすることができなくなります。

ユーザとの対話をはさんだビジネスプロセスにおいて、データベーストランザクションを維持するのはアプリケーション層の役割になります。よく使われる方法は、バージョン番号を使って、更新を管理する方法です。Hibernateは3つの方法で、Optimisticなトランザクション制御を行えます。

  • 自動バージョン付けのロングトランザクション
  • 自動バージョン付けのManyトランザクション
  • アプリケーションによるバージョンチェック

どのチェックの仕方にも共通するのが、バージョン番号を使って処理するということです。Hibernateが永続化処理する場合、バージョン番号も自動的に変更されます。

ロック制御

Hibernateは、排他ロックをサポートしています。通常、SELECT文というのは、共有ロックがかけれられます。共有ロックは、別のトランザクションから読み取りが許可されるロックです。

一連のトランザクションで見たときに、SELECTした値を変更する場合、排他ロックをかける必要があります。Hibernateではロック機構をLockModeクラスで表現しています。

LockMode.WRITE
行の挿入・更新時に自動的に取得されるロック
LockMode.UPGRADE
排他ロックを取得します。SELECT … FOR UPDATE 文が実行されます。
LockMode.UPGRADE_NOWAIT
SELECT … FOR UPDATE NO WAIT を実行します。(Oracleのみ)
LockMode.READ
分離レベルが Repeatable Read か Serializable の場合の読み込みのときに取得されます。
LockMode.NONE
ロックされていないことを表します。

O/Rマッピングを行う場合でも、トランザクションのことは忘れてはいけないのです。すべてをHibernateに任せることは間違っていて、トランザクションの分離レベルとロックトランザクション・Optimisticトランザクションの区別は、アプリケーション側で適切に考える必要があります。

参考

  • JSF / Spring / Hibernate を使ったアプリケーション開発
  • 開発者ノート「Hibernate」
  • トランザクション処理のバイブル
  • トランザクション処理のバイブル
  • トランザクション処理について、最初に学び始めるのにちょうど良いくらい