1. 概要

データベースからデータを取得したい場合はたくさんあります。 他の誰も私たちの行動を中断できないように、さらに処理するために自分でロックしたい場合があります。

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

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

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

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

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

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

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 ロックを保持すると、他のトランザクションがデータを読み取ったり、更新したり、削除したりできなくなります。

一部のデータベースシステムは、マルチバージョン同時実行制御を実装していることに注意してください。これにより、リーダーはすでにブロックされているデータをフェッチできます。

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. 悲観的なロックの使用

単一のレコードまたはレコードのグループにペシミスティックロックを設定する方法はいくつかあります。JPAでそれを行う方法を見てみましょう。

3.1. 探す

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

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

3.2. クエリ

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

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列挙型を使用できます。 NORMALEXTENDEDの2つの値が含まれています。

EntityManager 、[ X188X] 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

The 拡張スコープはと同じ機能をカバーします正常 。 加えて、 結合テーブル内の関連エンティティをブロックすることができます

簡単に言えば、@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

顧客テーブルの行と結合テーブルの行をロックする2つの「FORUPDATE」クエリがあることがわかります。

注意すべきもう1つの興味深い事実は、すべての永続性プロバイダーがロックスコープをサポートしているわけではないということです。

5. ロックタイムアウトの設定

ロックスコープの設定に加えて、別のロックパラメータであるtimeoutを調整できます。タイムアウト値は、LockTimeoutExceptionが発生するまでロックの取得を待機するミリ秒数です。

プロパティ’javax.persistence.lock.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でhibernateおよびEclipseLinkから入手できます。