JPAでの楽観的ロック

1.はじめに

エンタープライズアプリケーションに関しては、データベースへの同時アクセスを適切に管理することが重要です。これは、効果的かつ最も重要なこととして、エラーに強い方法で複数のトランザクションを処理できるはずであることを意味します。

さらに、同時読み取りと更新の間でデータの一貫性を保つ必要があります。

それを実現するために、Java Persistence APIが提供する楽観的ロック機構を使用できます。同じデータに対して同時に複数の更新を行っても、互いに干渉し合うことはありません。

2.楽観的ロックについて

楽観的ロックを使用するには、 @ Version__アノテーションを持つプロパティを含むエンティティが必要です 。それを使用している間、データを読み取る各トランザクションはversionプロパティの値を保持します。

トランザクションが更新を行おうとする前に、バージョンプロパティを再度チェックします。

その間に値が変更された場合は、 OptimisticLockException がスローされます。それ以外の場合、トランザクションは更新をコミットし、値バージョンプロパティをインクリメントします。

3.悲観的ロック対楽観的ロック

楽観的ロックとは対照的に、JPAは私たちに悲観的ロックを与えることを知っておくのは良いことです。データへの同時アクセスを処理するためのもう1つのメカニズムです。

ペシミスティックロックについては、以前の記事の1つである JPAにおけるペシミスティックロック で説明しています。違いと、それぞれの種類のロックからどのように利益を得ることができるかを見てみましょう。

前に述べたように、** 楽観的ロックはエンティティのバージョン属性をチェックしてエンティティの変更を検出することに基づいています。同時更新が行われると、 OptmisticLockException が発生します。その後、データの更新を再試行できます。

このメカニズムは、更新や削除よりもはるかに多くの読み取りを行うアプリケーションに適していると考えることができます。さらに、エンティティをしばらくデタッチしなければならず、ロックを保持できないような状況でも便利です。

反対に、悲観的ロックメカニズムはデータベースレベルでエンティティをロックすることを含みます。

各トランザクションはデータのロックを取得できます。ロックを保持している限り、トランザクションはロックされたデータの読み取り、削除、または更新を行うことはできません。悲観的ロックを使用するとデッドロックが発生する可能性があると推測できます。ただし、楽観的ロックよりもデータの完全性が保証されます。

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

整数 java.sql.Timestamp

  • エンティティを介してversion属性の値を取得できることを知っておく必要がありますが、それを更新またはインクリメントすることはできません。

永続性プロバイダがバージョン属性を持たないエンティティに対して楽観的ロックをサポートできることに注目する価値があります。

それでも、楽観的ロックを使用する場合は、常にバージョン属性を含めることをお勧めします。

そのような属性を含まないエンティティをロックしようとしたが、永続化プロバイダがそれをサポートしていないと、 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__ロックモードと同じ条件を満たす必要があります。 加えて、それはversion属性の値を増加させます しかし、それがすぐにされるべきであるかどうか、またはコミットまたはフラッシュまで延期されるかもしれないかどうかは特定されていません。

OPTIMISTIC ロックモードが要求された場合、永続化プロバイダは OPTIMISTIC INCREMENT__機能を提供できることを知っておく価値があります。

6.楽観的ロックの使用

  • バージョニングされたエンティティのために楽観的ロックがデフォルトで利用可能であることを覚えておくべきです** まだそれを明示的に要求するいくつかの方法があります。

6.1. 見つける

楽観的ロックを要求するために、適切な LockModeType EntityManager のメソッドを見つけるための引数として渡すことができます。

entityManager.find(Student.class, studentId, LockModeType.OPTIMISTIC);

6.2. 問い合わせ

ロックを有効にするもう1つの方法は、 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 がスローされます。 ** 例外のため、アクティブなトランザクションは常にロールバック用にマークされていることに注意してください。

OptimisticLockException にどのように対処できるかを知っておくと便利です。

都合のよいことに、この例外は衝突している実体への参照を含んでいます。 しかし、持続性プロバイダーがあらゆる状況でそれを提供することは必須ではありません 。オブジェクトが使用可能になるという保証はありません。

ただし、説明されている例外を処理するための推奨される方法があります。

リロードまたは更新によってエンティティを再度取得する必要があります。

できれば新しい取引で。その後、もう一度アップデートしてみることができます。

8.まとめ

このチュートリアルでは、並行トランザクションの調整に役立つツールについて詳しく説明しました。オプティミスティックロックでは、エンティティに含まれるバージョン属性を使用して、エンティティに対する同時変更を制御します。

したがって、更新や削除が上書きされたり、黙って失われたりすることはありません。悲観的ロックとは反対に、データベースレベルでエンティティをロックするわけではないため、DBのデッドロックに対して脆弱ではありません。

オプティミスティックロックは、バージョン対応エンティティに対してデフォルトで有効になっていることがわかりました。ただし、さまざまなロックモードタイプを使用して明示的に要求する方法はいくつかあります。

覚えておくべきもう1つの事実は、エンティティに矛盾する更新があるたびに、 OptimisticLockException が発生することを期待する必要があることです。

最後に、このチュートリアルのソースコードはhttps://github.com/eugenp/tutorials/tree/master/persistence-modules/hibernate5[GitHubで利用可能]です。