8.10.1 楽観的ロックの処理
楽観的ロックを使用すると,CJPAプロバイダはデータベースのデータがほかのアプリケーションから更新されていないかをユーザに代わってチェックします。データベースのデータが更新されていると,CJPAプロバイダは例外を発生させて,ユーザにデータが更新されていることを通知します。また,トランザクションをロールバックにマークします。
- 〈この項の構成〉
(1) データ更新の有無のチェック方法
データが更新されているかどうかは,データベースのテーブル上に用意したバージョン列の更新の有無によってチェックします。データベース上のデータが更新されると,バージョン列のバージョン番号が更新されます。これによって,ほかのアプリケーションなどからデータベースが更新されたことがわかります。データベースを更新するときのバージョン列の状態と動作について次の表に示します。
テーブルのバージョン列の状態 |
動作 |
---|---|
バージョン列の値が更新されていない場合 |
CJPAプロバイダは,エンティティの情報をデータベースに反映します。このとき,データベースのバージョン列の値を更新します。 |
バージョン列の値が更新されている場合 |
ほかのアプリケーションなどからデータベースのデータが更新されていることを表します。このため,CJPAプロバイダはOptimisticLockExceptionを発生させて,トランザクションをロールバックにマークします。 |
このように,バージョン列の状態によって,エンティティを読み込んだ状態からデータベースを更新するまでに,ほかのトランザクションがデータを更新していないことを保証できます。
(2) 永続化フィールドおよびリレーションシップのバージョンチェック
楽観的ロックを使用するには,エンティティの永続化フィールドおよびリレーションシップの両方をバージョンチェックの対象にします。バージョンチェックの対象とするには,エンティティにバージョン列に対応するVersionフィールド(プロパティ)を設定してください。Versionフィールドは@VersionまたはO/Rマッピングファイルの<version>タグを使用して設定します。
エンティティのバージョンチェックは次のどちらかのタイミングで実行されます。
-
エンティティの状態が変更され,その変更をデータベースに書き込むとき
-
merge処理によって,エンティティがmanaged状態に変更されたとき※
- 注※
-
バージョンチェックはmerge実行時だけでなく,flushまたはトランザクションのコミット時にもチェックが実行されます。
バージョンチェックによってエンティティのバージョンが古いことが判明した場合,OptimisticLockExceptionが発生します。また,トランザクションはロールバックにマークされます。
(3) flush操作またはトランザクションの決着時のバージョンチェック
エンティティをflush操作またはトランザクションの決着時にバージョンチェックの対象にできます。バージョンチェックの対象とするには,EntityManagerのlockメソッドにエンティティを指定します。EntityManagerのlockメソッドを使用することで,トランザクションでのバージョンチェック対象にエンティティを追加したり,バージョン列の更新方針を変更したりできます。
CJPAプロバイダでは,バージョン列の更新タイミング(lockメソッドのLockModeType)としてLockModeType.READおよびLockModeType.WRITEの二つをサポートしています。バージョン列の更新タイミングの指定内容にかかわらず,CJPAプロバイダではトランザクションの決着時に次に示す二つの事象が起きないことを保証します。
-
ダーティリード(Dirty Read)
トランザクション「T1」が行を変更します。次に,「T1」がコミットやロールバックを行う前に別のトランザクション「T2」が同じ行を読み込み,変更した値を取得します。最終的に「T2」がコミットに成功します。
「T1」がコミットかロールバックをするかどうかが重要ではなく,「T2」のコミットの前またはあとのどちらで「T1」のコミットかロールバックが行われるかが重要になります。
-
繰り返し不可能な読み込み(un-Repeatable Read)
トランザクション「T1」が行を読み込みます。次に,「T1」がコミットする前に,もう一方のトランザクション「T2」がその行の変更や削除を実施します。最終的に両方のトランザクションはコミットに成功します。
LockModeTypeとしてLockModeType.WRITEを指定すると,エンティティの状態変更がない場合でもバージョン列は強制的に更新されます。バージョン列の更新はflushまたはトランザクションのコミットが呼び出されたタイミングで実行されます。なお,バージョン列の更新前にエンティティが削除された場合,バージョン列の更新は省略されることもあります。