JPAにおける悲観的ロック

1.概要

データベースからデータを取得したい場合はたくさんあります。他の人が私たちの行動を妨げることができないように、時々私たちはさらなる処理のために自分自身でそれをロックしたいと思います。

  • 適切なトランザクション分離レベルを設定すること、または現在必要なデータをロックすることを可能にする2つの並行性制御メカニズムを考えることができます。**

トランザクション分離は、データベース接続に対して定義されています。さまざまな程度のロックデータを保持するように設定できます。

ただし、独立性レベルは、接続が作成されると設定され、その接続内のすべてのステートメントに影響します。幸いなことに、データへのよりきめ細かい排他アクセスを予約するためにデータベースメカニズムを使用する悲観的ロックを使用できます。

悲観的ロックを使用して、他のトランザクションが予約データを変更または削除できないようにすることができます。

保持できるロックには、排他ロックと共有ロックの2種類があります。他の誰かが共有ロックを保持していると、データを読み取ることはできますが、書き込むことはできません。予約データを変更または削除するには、排他ロックをかける必要があります。

SELECT …​ FOR UPDATE ステートメントを使用して、排他ロックを取得できます。

2.ロックモード

JPA仕様では、これから議論する3つの悲観的ロックモードを定義しています。

  • PESSIMISTIC READ__ - 共有ロックを取得して、

更新または削除されたデータ ** PESSIMISTIC WRITE__ - 排他ロックを取得することができます

データの読み取り、更新、削除を防ぎます。 ** PESSIMISTIC FORCE INCREMENT - PESSIMISTIC WRITE__と同じように動作します

バージョン対応エンティティのバージョン属性を追加的にインクリメント

それらはすべて LockModeType クラスの静的メンバーであり、トランザクションがデータベースロックを取得できるようにします。それらはすべて、トランザクションがコミットまたはロールバックされるまで保持されます。

  • 一度に取得できるロックは1つだけです。それが不可能な場合は、 PersistenceException がスローされます。

2.1. PESSIMISTIC READ__

単にデータを読み、ダーティリードに遭遇したくないときはいつでも、 PESSIMISTIC READ__(共有ロック)を使うことができます。ただし、更新や削除はできません。**

私たちが使っているデータベースが PESSIMISTIC READ ロックをサポートしていない場合があるので、代わりに PESSIMISTIC WRITE ロックを取得することが可能です。

2.2. PESSIMISTIC WRITE__

データのロックを取得して変更する必要があるトランザクションは、 PESSIMISTIC WRITE ロックを取得する必要があります。 JPA 仕様によると、 PESSIMISTIC WRITE ロックを保持すると、他のトランザクションによるデータの読み取り、更新、削除ができなくなります。

  • いくつかのデータベースシステムは、読者がすでにブロックされているデータを取得することを可能にするhttps://en.wikipedia.org/wiki/Multiversion concurrency control[multi-version concurrency control]を実装することに注意してください。

2.3. PESSIMISTIC FORCE INCREMENT

このロックは PESSIMISTIC WRITE と同じように機能しますが、バージョン管理されたエンティティ、つまり属性が@ @ Version でアノテートされたエンティティと連携するために導入されました。

バージョニングされたエンティティの更新は、 PESSIMISTIC FORCE INCREMENT ロックを取得することよりも先に起こる可能性があります。そのロックを取得すると、バージョン列が更新されます。

バージョン管理されていないエンティティに対して PESSIMISTIC FORCE INCREMENT がサポートされているかどうかを判断するのは、永続化プロバイダ次第です。そうでない場合は、__PersistanceExceptionがスローされます。

2.4. 例外

ペシミスティックロックを使用しているときにどの例外が発生する可能性があるかを知っておくと便利です。 JPA 仕様はさまざまなタイプの例外を提供します。

  • PessimisticLockException - ロックを取得していることを示します。

共有ロックを排他ロックに変換すると失敗し、 トランザクションレベルのロールバック ** LockTimeoutException - は、ロックを取得していることを示します。

共有ロックを排他的タイムアウトに変換すると、 文レベルのロールバック ** __PersistanceException - 永続性の問題を示します

発生した。 PersistanceException およびそのサブタイプ(ただし、 NoResultException NonUniqueResultException、 LockTimeoutException 、および____QueryTimeoutExceptionを除く)は、アクティブなトランザクションがロールバックされることを示します。

3.ペシミスティックロックの使用

  • 単一のレコードまたはレコードのグループに対して悲観的ロックを設定する方法はいくつかあります。

3.1. 見つける

それはおそらく最も直接的な方法です。 LindModeType オブジェクトを find メソッドのパラメータとして渡すだけで十分です。

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

3.2. 問い合わせ

さらに、 Query オブジェクトを使用して、ロックモードをパラメータとして setLockMode setterを呼び出すこともできます。

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

3.3. 明示的ロック

findメソッドによって取得された結果を手動でロックすることもできます。

Student resultStudent = entityManager.find(Student.class, studentId);
entityManager.lock(resultStudent, LockModeType.PESSIMISTIC__WRITE);

3.4. 更新する

エンティティの状態を __refresh __メソッドで上書きしたい場合は、ロックを設定することもできます。

Student resultStudent = entityManager.find(Student.class, studentId);
entityManager.refresh(resultStudent, LockModeType.PESSIMISTIC__FORCE__INCREMENT);

3.5. NamedQuery

@ NamedQuery アノテーションを使用すると、ロックモードも設定できます。

@NamedQuery(name="lockStudent",
  query="SELECT s FROM Student s WHERE s.id LIKE :studentId",
  lockMode = PESSIMISTIC__READ)

4.ロック範囲

  • ロック範囲パラメータは、ロックされたエンティティのロック関係をどのように処理するかを定義します** クエリで定義された単一のエンティティだけでロックを取得したり、さらにその関係をブロックすることができます。

スコープを設定するために PessimisticLockScope enumを使うことができます。 NORMAL EXTENDED の2つの値が含まれています。

PessimisticLockScope の値を持つパラメータ ' javax.persistance.lock.scope 'を EntityManager Query TypedQuery 、または NamedQuery の適切なメソッドへの引数として渡すことでスコープを設定できます。

Map<String, Object> properties = new HashMap<>();
map.put("javax.persistence.lock.scope", PessimisticLockScope.EXTENDED);

entityManager.find(
  Student.class, 1L, LockModeType.PESSIMISTIC__WRITE, properties);

4.1. PessimisticLockScope.NORMAL

  • PessimisticLockScope.NORMAL がデフォルトの範囲であることを知っておく必要があります** このロック範囲では、エンティティ自体をロックします。結合継承と共に使用すると、先祖もロックされます。

2つのエンティティを含むサンプルコードを見てみましょう。

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Person {

    @Id
    private Long id;
    private String name;
    private String lastName;

   //getters and setters
}

@Entity
public class Employee extends Person {

    private BigDecimal salary;

   //getters and setters
}

Employee のロックを取得したい場合は、これら2つのエンティティにまたがる SQL クエリを観察できます。

SELECT t0.ID, t0.DTYPE, t0.LASTNAME, t0.NAME, t1.ID, t1.SALARY
FROM PERSON t0, EMPLOYEE t1
WHERE ((t0.ID = ?) AND ((t1.ID = t0.ID) AND (t0.DTYPE = ?))) FOR UPDATE

4.2. PessimisticLockScope.EXTENDED

EXTENDED スコープは__NORMALと同じ機能をカバーします。さらに、 結合テーブル内の関連エンティティをブロックすることができます

簡単に言うと、 @ ElementCollection または @ OneToOne @ OneToMany などのアノテーションが付いたエンティティ、および @ JoinTable で機能します。

@ ElementCollection アノテーションを付けたサンプルコードを見てみましょう。

@Entity
public class Customer {

    @Id
    private Long customerId;
    private String name;
    private String lastName;
    @ElementCollection
    @CollectionTable(name = "customer__address")
    private List<Address> addressList;

   //getters and setters
}

@Embeddable
public class Address {

    private String country;
    private String city;

   //getters and setters
}

Customer エンティティを検索するときに、いくつかのクエリを分析しましょう。

SELECT CUSTOMERID, LASTNAME, NAME
FROM CUSTOMER WHERE (CUSTOMERID = ?) FOR UPDATE

SELECT CITY, COUNTRY, Customer__CUSTOMERID
FROM customer__address
WHERE (Customer__CUSTOMERID = ?) FOR UPDATE

customerテーブルの行とjoinテーブルの行をロックする2つの ' FOR UPDATE 'クエリがあります。

私たちが知っておくべきもう一つの興味深い事実は、すべての永続プロバイダがロックスコープをサポートしているわけではないということです。

5.ロックタイムアウトを設定する

ロック範囲を設定する以外に、別のロックパラメータ - タイムアウトを調整できます。 ** タイムアウト値は、 LockTimeoutException が発生するまでロックを取得するのを待つミリ秒数です。

プロパティ「__javax.persistence.lock.timeout」を適切なミリ秒数で使用することで、ロックスコープと同様にtimeoutの値を変更できます。

タイムアウト値をゼロに変更して「待機しない」ロックを指定することもできます。ただし、この方法でタイムアウト値を設定することをサポートしていないデータベースドライバがあることに注意してください。

Map<String, Object> properties = new HashMap<>();
map.put("javax.persistence.lock.timeout", 1000L);

entityManager.find(
  Student.class, 1L, LockModeType.PESSIMISTIC__READ, properties);

6.まとめ

適切な分離レベルを設定しても並行トランザクションに対処するのに十分ではない場合、JPAは悲観的ロックを提供します。異なるトランザクションを分離して調整し、同じリソースに同時にアクセスしないようにすることができます。

それを達成するために、議論されているタイプのロックの中から選択し、その結果そのようなパラメータをそれらのスコープまたはタイムアウトのように修正することができます。

一方、データベースロックを理解することは、基盤となるデータベースシステムのメカニズムを理解することと同じくらい重要であることを忘れないでください。悲観的ロックの振る舞いは、私たちが協力している永続化プロバイダに依存するということを心に留めておくことも重要です。

最後に、このチュートリアルのソースコードはGitHubで入手できます。チュートリアル/ツリー/マスター/永続モジュール/spring-data-eclipselink/src/test/java/com/baeldung/eclipselink/springdata/pessimisticlocking[EclipseLink]。