JPAでの楽観的ロック

JPAの楽観的ロック

1. 前書き

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

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

それを実現するために、Java Persistence APIが提供する楽観的ロックメカニズムを使用できます。 同じデータに対して同時に行われた複数の更新が互いに干渉しないことを意味します。

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

楽観的ロックを使用するには、we need to have an entity including a property with @Version annotation。 使用中、データを読み取る各トランザクションは、バージョンプロパティの値を保持します。

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

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

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

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

以前の記事の1つであるPessimistic Locking in JPAで、悲観的なロックについて説明します。 違いは何であり、各タイプのロックからどのように利益を得ることができるかを見てみましょう。

前に言ったように、optimistic locking is based on detecting changes on entities by checking their version attribute。 同時更新が行われると、OptmisticLockExceptionが発生します。 その後、データの更新を再試行できます。

このメカニズムは、更新や削除よりもはるかに多くの読み取りを行うアプリケーションに適していると想像できます。 さらに、エンティティをしばらくの間切り離す必要があり、ロックを保持できない状況で役立ちます。

それどころか、悲観的なロックメカニズムには、データベースレベルでエンティティをロックすることが含まれます。

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

4. バージョン属性

バージョン属性は、@Versionアノテーションが付いたプロパティです。 They are necessary for enabling optimistic locking.サンプルエンティティクラスを見てみましょう。

@Entity
public class Student {

    @Id
    private Long id;

    private String name;

    private String lastName;

    @Version
    private Integer version;

    // getters and setters

}

バージョン属性を宣言する際に従うべきいくつかのルールがあります。

  • 各エンティティクラスには1つのバージョン属性のみが必要です

  • 複数のテーブルにマッピングされたエンティティのプライマリテーブルに配置する必要があります

  • バージョン属性のタイプは、intIntegerlongLongshortShortjava.sql.Timestamp

We should know that we can retrieve a value of the version attribute via entity, but we mustn’t update or increment it.永続性プロバイダーのみがそれを実行できるため、データの一貫性が保たれます。

永続性プロバイダーは、バージョン属性を持たないエンティティの楽観的ロックをサポートできることに注意してください。 ただし、楽観的ロックを使用する場合は、常にバージョン属性を含めることをお勧めします。

そのような属性を含まないエンティティをロックしようとし、永続性プロバイダーがそれをサポートしていない場合、結果はPersitenceExceptionになります。

5. ロックモード

JPAは、2つの異なる楽観的ロックモード(および2つのエイリアス)を提供します。

  • OPTIMISTIC –バージョン属性を含むすべてのエンティティに対して楽観的な読み取りロックを取得します

  • OPTIMISTIC_FORCE_INCREMENTOPTIMISTICと同じ楽観的ロックを取得し、さらにバージョン属性値をインクリメントします

  • READ –これはOPTIMISTICの同義語です

  • WRITE –これはOPTIMISTIC_FORCE_INCREMENTの同義語です

上記のすべてのタイプは、LockModeTypeクラスにあります。

5.1. OPTIMISTICREAD

すでに知っているように、OPTIMISTICREADのロックモードは同義語です。 ただし、JPA仕様では、新しいアプリケーションでOPTIMISTICを使用することを推奨しています。

Whenever we request the OPTIMISTIC lock mode, a persistence provider will prevent our data from dirty reads as well as non-repeatable reads

簡単に言えば、トランザクションが別のトランザクションのデータの変更をコミットできないことを確認する必要があります。

  • 更新または削除されたが、コミットされていない

  • その間に正常に更新または削除されました

5.2. OPTIMISTIC_INCREMENTWRITE

以前と同じように、OPTIMISTIC_INCREMENTWRITEは同義語ですが、前者の方が望ましいです。

OPTIMISTIC_INCREMENTは、OPTIMISTICロックモードと同じ条件を満たす必要があります。 Additionally, it increments the value of a version attribute.ただし、すぐに実行するか、コミットまたはフラッシュするまで延期するかは指定されていません。

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

6. 楽観的ロックの使用

We should remember that for versioned entities optimistic locking is available by default.それでも、明示的に要求する方法はいくつかあります。

6.1. Find

楽観的ロックをリクエストするには、適切な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

When the persistence provider discovers optimistic locking conflicts on entities, it throws OptimisticLockException例外のため、アクティブなトランザクションは常にロールバックのマークが付けられていることに注意してください。

OptimisticLockExceptionにどのように反応できるかを知っておくとよいでしょう。 便利なことに、この例外には競合するエンティティへの参照が含まれています。 However, it’s not mandatory for the persistence provider to supply it in every situation。 オブジェクトが使用可能になる保証はありません。

ただし、説明されている例外を処理する推奨方法があります。 再読み込みまたは更新して、エンティティを再度取得する必要があります。 できれば新しいトランザクションで。 その後、もう一度更新を試みることができます。

8. 結論

このチュートリアルでは、同時トランザクションのオーケストレーションに役立つツールに慣れました。 Optimistic locking uses version attributes included in entities to control concurrent modifications on them.

したがって、更新や削除が上書きされたり、サイレントに失われたりすることがなくなります。 ペシミスティックロックとは異なり、データベースレベルでエンティティをロックしないため、DBデッドロックに対して脆弱ではありません。

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

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

最後に、このチュートリアルのソースコードはover on GitHubで入手できます。