JPAでのオプティミスティックロック
1. 序章
エンタープライズアプリケーションに関しては、データベースへの同時アクセスを適切に管理することが重要です。 これは、複数のトランザクションを効果的かつ最も重要なエラー防止の方法で処理できる必要があることを意味します。
さらに、同時読み取りと更新の間でデータの一貫性を維持する必要があります。
これを実現するために、JavaPersistenceAPIによって提供される楽観的なロックメカニズムを使用できます。 これにより、同じデータに対して同時に行われた複数の更新が相互に干渉することはありません。
2. 楽観的なロックを理解する
楽観的ロックを使用するには、@Versionアノテーション付きのプロパティを含むエンティティが必要です。 それを使用している間、データを読み取る各トランザクションはバージョンプロパティの値を保持します。
トランザクションが更新を行う前に、バージョンプロパティを再度チェックします。
その間に値が変更された場合、OptimisticLockExceptionがスローされます。 それ以外の場合、トランザクションは更新をコミットし、値バージョンプロパティをインクリメントします。
3. 悲観的なロックと 楽観的なロック
楽観的ロックとは対照的に、JPAは悲観的ロックを提供することを知っておくとよいでしょう。 これは、データの同時アクセスを処理するためのもう1つのメカニズムです。
以前の記事の1つであるJPAでのペシミスティックロックについて説明します。 違いは何であり、各タイプのロックからどのように利益を得ることができるかを調べてみましょう。
前に述べたように、楽観的ロックは、バージョン属性をチェックすることによってエンティティの変更を検出することに基づいています。 同時更新が発生すると、OptisticLockExceptionが発生します。 その後、データの更新を再試行できます。
このメカニズムは、更新や削除よりもはるかに多くの読み取りを行うアプリケーションに適していると想像できます。 さらに、エンティティをしばらくの間切り離す必要があり、ロックを保持できない状況で役立ちます。
それどころか、悲観的なロックメカニズムには、データベースレベルでエンティティをロックすることが含まれます。
各トランザクションは、データのロックを取得できます。 ロックを保持している限り、トランザクションはロックされたデータを読み取ったり、削除したり、更新したりすることはできません。 悲観的なロックを使用すると、デッドロックが発生する可能性があると推測できます。 ただし、楽観的なロックよりもデータの整合性が高くなります。
4. バージョン属性
バージョン属性は、@Versionアノテーションが付いたプロパティです。 楽観的ロックを有効にするために必要です。サンプルエンティティクラスを見てみましょう。
@Entity
public class Student {
@Id
private Long id;
private String name;
private String lastName;
@Version
private Integer version;
// getters and setters
}
バージョン属性を宣言する際に従う必要のあるいくつかのルールがあります。
- 各エンティティクラスには、バージョン属性が1つだけ必要です。
- 複数のテーブルにマップされたエンティティのプライマリテーブルに配置する必要があります
- バージョン属性のタイプは、 int 、 Integer 、 long 、 Long 、shortのいずれかである必要があります。 ]、 Short 、 java.sql.Timestamp
永続性プロバイダーは、バージョン属性を持たないエンティティの楽観的ロックをサポートできることに注意してください。 ただし、楽観的ロックを使用する場合は、常にバージョン属性を含めることをお勧めします。
そのような属性を含まないエンティティをロックしようとし、永続性プロバイダーがそれをサポートしない場合、PersitenceExceptionが発生します。
5. ロックモード
JPAは、2つの異なる楽観的ロックモード(および2つのエイリアス)を提供します。
- OPTIMISTIC –バージョン属性を含むすべてのエンティティの楽観的な読み取りロックを取得します
- OPTIMISTIC_FORCE_INCREMENT – OPTIMISTIC と同じ楽観的ロックを取得し、さらにバージョン属性値をインクリメントします
- READ – OPTIMISTICの同義語です
- WRITE –これはOPTIMISTIC_FORCE_INCREMENTの同義語です。
上記のすべてのタイプは、LockModeTypeクラスにあります。
5.1. OPTIMISTIC ( READ )
すでに知っているように、OPTIMISTICおよびREADロックモードは同義語です。 ただし、JPA仕様では、新しいアプリケーションでOPTIMISTICを使用することを推奨しています。
OPTIMISTICロックモードを要求するときはいつでも、永続性プロバイダーは、データがダーティリードおよび繰り返し不可能なリードになるのを防ぎます。
簡単に言えば、トランザクションが別のトランザクションのデータに対する変更をコミットできないことを確認する必要があります。
- 更新または削除されましたが、コミットされていません
- その間に正常に更新または削除されました
5.2. OPTIMISTIC_INCREMENT ( WRITE )
以前と同じように、OPTIMISTIC_INCREMENTとWRITEは同義語ですが、前者の方が望ましいです。
OPTIMISTIC_INCREMENT は、OPTIMISTICロックモードと同じ条件を満たす必要があります。 さらに、バージョン属性の値をインクリメントします。ただし、すぐに実行するか、コミットまたはフラッシュするまで延期するかは指定されていません。
OPTIMISTIC ロックモードが要求された場合、永続性プロバイダーがOPTIMISTIC_INCREMENT機能を提供できることを知っておく価値があります。
6. 楽観的ロックの使用
バージョン管理されたエンティティの場合、デフォルトで楽観的ロックが使用可能であることを覚えておく必要があります。ただし、明示的に要求する方法はいくつかあります。
6.1. 探す
楽観的なロックを要求するには、適切な LockModeType を引数として渡して、EntityManagerのメソッドを検索します。
entityManager.find(Student.class, studentId, LockModeType.OPTIMISTIC);
6.2. クエリ
ロックを有効にする別の方法は、QueryオブジェクトのsetLockModeメソッドを使用することです。
Query query = entityManager.createQuery("from Student where id = :id");
query.setParameter("id", studentId);
query.setLockMode(LockModeType.OPTIMISTIC_INCREMENT);
query.getResultList()
6.3. 明示的なロック
EnitityManagerのlockメソッドを呼び出すことにより、ロックを設定できます。
Student student = entityManager.find(Student.class, id);
entityManager.lock(student, LockModeType.OPTIMISTIC);
6.4. 更新
前のメソッドと同じ方法でrefreshメソッドを呼び出すことができます。
Student student = entityManager.find(Student.class, id);
entityManager.refresh(student, LockModeType.READ);
6.5. NamedQuery
最後のオプションは、lockModeプロパティで@NamedQueryを使用することです。
@NamedQuery(name="optimisticLock",
query="SELECT s FROM Student s WHERE s.id LIKE :id",
lockMode = WRITE)
7. OptimisticLockException
OptimisticLockExceptionにどのように対応できるかを知っておくとよいでしょう。 便利なことに、この例外には競合するエンティティへの参照が含まれています。 ただし、永続性プロバイダーがすべての状況でそれを提供することは必須ではありません。 オブジェクトが使用可能になるという保証はありません。
ただし、説明されている例外を処理するための推奨される方法があります。 リロードまたは更新して、エンティティを再度取得する必要があります。 できれば新しいトランザクションで。 その後、もう一度更新を試みることができます。
8. 結論
このチュートリアルでは、並行トランザクションの調整に役立つツールについて理解しました。 オプティミスティックロックは、エンティティに含まれるバージョン属性を使用して、エンティティの同時変更を制御します。
したがって、更新または削除が上書きされたり、サイレントに失われたりしないことが保証されます。 悲観的なロックとは反対に、データベースレベルでエンティティをロックしないため、DBデッドロックに対して脆弱ではありません。
バージョン管理されたエンティティに対して、デフォルトで楽観的ロックが有効になっていることを学びました。 ただし、さまざまなロックモードタイプを使用して明示的に要求する方法はいくつかあります。
覚えておくべきもう1つの事実は、エンティティで競合する更新があるたびに、OptimisticLockExceptionを予期する必要があるということです。
最後に、このチュートリアルのソースコードは、GitHubからで入手できます。