JPAでの楽観的ロック

1.はじめに

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

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

これを実現するために、Java PersistenceAPIによって提供される楽観的ロックメカニズムを使用できます。これにより、同じデータに対して同時に行われた複数の更新が互いに干渉することはありません。

2.楽観的ロックを理解する

楽観的ロックを使用するには、@ 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つだけ必要です。
  • 複数のテーブルにマップされたエンティティのプライマリテーブルに配置する必要があります
  • バージョン属性のタイプは、intIntegerlongLongshortShortjava.sql.Timestampのいずれかである必要があります。

エンティティを介してバージョン属性の値を取得できることを知っておく必要がありますが、それを更新またはインクリメントしてはなりません。永続性プロバイダーのみがそれを実行できるため、データの一貫性が保たれます。

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

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

5.ロックモード

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

  • OPTIMISTIC –バージョン属性を含むすべてのエンティティの楽観的な読み取りロックを取得します
  • OPTIMISTIC_FORCE_INCREMENTOPTIMISTICと同じ楽観的ロックを取得し、さらにバージョン属性値をインクリメントします
  • READ -それは同義語だOPTIMISTIC
  • 書き込み–これはOPTIMISTIC_FORCE_INCREMENTの同義語です

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

5.1。OPTIMISTICREAD

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

OPTIMISTICロックモードを要求するときはいつでも、永続性プロバイダーは、データがダーティな読み取りや繰り返し不可能な読み取りから保護されます

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

  • 更新または削除されましたが、コミットされていません
  • その間に正常に更新または削除されました

5.2。OPTIMISTIC_INCREMENTWRITE

The same as previously, OPTIMISTIC_INCREMENT and WRITE are synonyms, but the former is preferable.

OPTIMISTIC_INCREMENT must meet the same conditions as OPTIMISTIC lock mode. Additionally, it increments the value of a version attribute. However, it's not specified whether it should be done immediately or may be put off until commit or flush.

It's worth to know that a persistence provider is allowed to provide OPTIMISTIC_INCREMENT functionality when OPTIMISTIC lock mode is requested.

6. Using Optimistic Locking

We should remember that for versioned entities optimistic locking is available by default. Yet there are several ways of requesting it explicitly.

6.1. Find

To request optimistic locking we can pass the proper LockModeType as an argument to find method of EntityManager:

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

6.2. Query

Another way to enable locking is using the setLockMode method of Query object:

Query query = entityManager.createQuery("from Student where id = :id"); query.setParameter("id", studentId); query.setLockMode(LockModeType.OPTIMISTIC_INCREMENT); query.getResultList()

6.3. Explicit Locking

We can set a lock by calling EnitityManager's lock method:

Student student = entityManager.find(Student.class, id); entityManager.lock(student, LockModeType.OPTIMISTIC);

6.4. Refresh

We can call the refresh method the same way as the previous method:

Student student = entityManager.find(Student.class, id); entityManager.refresh(student, LockModeType.READ);

6.5. NamedQuery

The last option is to use @NamedQuery with the lockMode property:

@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. We should be aware that due to the exception the active transaction is always marked for rollback.

It's good to know how we can react to OptimisticLockException. Conveniently, this exception contains a reference to the conflicting entity. However, it's not mandatory for the persistence provider to supply it in every situation. There is no guarantee that the object will be available.

There is a recommended way of handling the described exception, though. We should retrieve the entity again by reloading or refreshing. Preferably in a new transaction. After that, we can try to update it once more.

8. Conclusion

In this tutorial, we got familiar with a tool which can help us orchestrate concurrent transactions. Optimistic locking uses version attributes included in entities to control concurrent modifications on them.

Therefore, it ensures that any updates or deletes won't be overwritten or lost silently. Opposite to pessimistic locking, it doesn't lock entities on the database level and consequently, it isn't vulnerable to DB deadlocks.

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

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

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