この長い答えを怖がらないでください。このトピックは単純ではありません。
デフォルトで
は、ロックを指定しない場合、 JPA はコミットされた読み取りLockModeType.NONE
分離レベルを課します ( を使用した場合と同じ動作)。
リードコミットには、ダーティリード現象が存在しないことが必要です。T1 は、T2 がコミットした後に T2 によって行われた変更のみを確認できます。
JPA で楽観的ロックを使用すると、分離レベルが
Repetable readに上がります。
T1 がトランザクションの開始時と終了時にデータを読み取る場合、T2 がデータを変更し、T1 の途中でコミットした場合でも、反復可能な読み取りにより、T1 が同じデータを参照することが保証されます。
そして、ここでトリッキーな部分が来ます。JPAは、繰り返し可能でない読み取り現象を防止することにより、可能な限り最も単純な方法で繰り返し可能読み取りを実現します。JPA は、読み取りのスナップショットを保持できるほど洗練されていません。例外を発生させることによって、2 回目の読み取りが発生するのを防ぐだけです (データが最初の読み取りから変更された場合)。
次の 2 つのオプティミスティック ロック オプションから選択できます。
2つの違いは何ですか?
Person
このエンティティの例を示しましょう。
@Entity
public class Person {
@Id int id;
@Version int version;
String name;
String label;
@OneToMany(mappedBy = "person", fetch = FetchType.EAGER)
List<Car> cars;
// getters & setters
}
ここで、 Johnという名前の Personがデータベースに保存されているとします。T1 でこの Person を読み取りましたが、2 番目のトランザクション T2 で彼の名前をMikeに変更します。
ロックなし
Person person1 = em1.find(Person.class, id, LockModeType.NONE); //T1 reads Person("John")
Person person2 = em2.find(Person.class, id); //T2 reads Person("John")
person2.setName("Mike"); //Changing name to "Mike" within T2
em2.getTransaction().commit(); // T2 commits
System.out.println(em1.find(Person.class, id).getName()); // prints "John" - entity is already in Persistence cache
System.out.println(
em1.createQuery("SELECT count(p) From Person p where p.name='John'")
.getSingleResult()); // prints 0 - ups! don't know about any John (Non-repetable read)
楽観的な読み取りロック
Person person1 = em1.find(Person.class, id, LockModeType.OPTIMISTIC); //T1 reads Person("John")
Person person2 = em2.find(Person.class, id); //T2 reads Person("John")
person2.setName("Mike"); //Changing name to "Mike" within T2
em2.getTransaction().commit(); // T2 commits
System.out.println(
em1.createQuery("SELECT count(p) From Person p where p.name='John'")
.getSingleResult()); // OptimisticLockException - The object [Person@2ac6f054] cannot be updated because it has changed or been deleted since it was last read.
LockModeType.OPTIMISTIC_FORCE_INCREMENT
変更が他のエンティティ (おそらく所有されていない関係) に加えられ、整合性を維持したい場合に使用されます。ジョンが新しい車を購入した例を説明しましょう。
楽観的な読み取りロック
Person john1 = em1.find(Person.class, id); //T1 reads Person("John")
Person john2 = em2.find(Person.class, id, LockModeType.OPTIMISTIC); //T2 reads Person("John")
//John gets a mercedes
Car mercedes = new Car();
mercedes.setPerson(john2);
em2.persist(mercedes);
john2.getCars().add(mercedes);
em2.getTransaction().commit(); // T2 commits
//T1 doesn't know about John's new car. john1 in stale state. We'll end up with wrong info about John.
if (john1.getCars().size() > 0) {
john1.setLabel("John has a car");
} else {
john1.setLabel("John doesn't have a car");
}
em1.flush();
楽観的な書き込みロック
Person john1 = em1.find(Person.class, id); //T1 reads Person("John")
Person john2 = em2.find(Person.class, id, LockModeType.OPTIMISTIC_FORCE_INCREMENT); //T2 reads Person("John")
//John gets a mercedes
Car mercedes = new Car();
mercedes.setPerson(john2);
em2.persist(mercedes);
john2.getCars().add(mercedes);
em2.getTransaction().commit(); // T2 commits
//T1 doesn't know about John's new car. john1 in stale state. That's ok though because proper locking won't let us save wrong information about John.
if (john1.getCars().size() > 0) {
john1.setLabel("John has a car");
} else {
john1.setLabel("John doesn't have a car");
}
em1.flush(); // OptimisticLockException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
JPAの仕様に次のような記述がありますが、HibernateとEclipseLinkはうまく動作するので使用しません。
バージョン管理されたオブジェクトの場合、実装が LockModeType.OPTIMISTIC が要求された場所で LockMode-Type.OPTIMISTIC_FORCE_INCREMENT を使用することは許可されていますが、その逆は許可されていません。