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]。