23

JPA とロック モードに関するOracle のブログの記事を読みました

OPTIMISTICOPTIMISTIC_FORCE_INCREMENTロック モード タイプの違いがよくわかりません。

OPTIMISTICモード:

ここに画像の説明を入力

ユーザーがこのモードでエンティティをロックする@versionと、トランザクションの開始時にバージョン フィールド エンティティ ( ) のチェックが行われ、トランザクションの終了時にバージョン フィールドのチェックも行われます。バージョンが異なる場合、トランザクションはロールバックされます。

OPTIMISTIC_FORCE_INCREMENTモード:

ここに画像の説明を入力

ユーザーがこのモードを選択すると、EntityManager の状態をデータベースに flush() して、バージョン フィールドを手動でインクリメントする必要があります。したがって、他のすべての楽観的なトランザクションは無効になります (ロールバック)。トランザクションのコミットまたはロールバックのために、トランザクションの最後にバージョンのチェックも行われます。

明らかなようですが、いつOPTIMISTICvsOPTIMISTIC_FORCE_INCREMENTモードを使用する必要がありますか? 私が見る唯一の基準はOPTIMISTIC_FORCE_INCREMENT、トランザクションを他のトランザクションよりも優先させたい場合にモードを適用することです。このモードを選択すると、実行中の他のすべてのトランザクションがロールバックされるためです (メカニズムをよく理解している場合)。

モードではなくこのモードを選択する他の理由はありOPTIMISTICますか?

ありがとう

4

3 に答える 3

50

この長い答えを怖がらないでください。このトピックは単純ではありません。

デフォルトで は、ロックを指定しない場合、 JPA はコミットされた読み取りLockModeType.NONE分離レベルを課します ( を使用した場合と同じ動作)。

リードコミットには、ダーティリード現象が存在しないことが必要です。T1 は、T2 がコミットした後に T2 によって行われた変更のみを確認できます。

JPA で楽観的ロックを使用すると、分離レベルが Repetable readに上がります。

T1 がトランザクションの開始時と終了時にデータを読み取る場合、T2 がデータを変更し、T1 の途中でコミットした場合でも、反復可能な読み取りにより、T1 が同じデータを参照することが保証されます。

そして、ここでトリッキーな部分が来ます。JPAは、繰り返し可能でない読み取り現象を防止することにより、可能な限り最も単純な方法で繰り返し可能読み取りを実現します。JPA は、読み取りのスナップショットを保持できるほど洗練されていません。例外を発生させることによって、2 回目の読み取りが発生するのを防ぐだけです (データが最初の読み取りから変更された場合)。

次の 2 つのオプティミスティック ロック オプションから選択できます。

  • LockModeType.OPTIMISTIC( LockModeType.READJPA 1.0 で)

  • LockModeType.OPTIMISTIC_FORCE_INCREMENT( LockModeType.WRITEJPA 1.0 で)

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 を使用することは許可されていますが、その逆は許可されていません。

于 2014-08-26T13:48:40.797 に答える
15

通常、楽観的ロックに lock() API を使用することはありません。JPA は、更新または削除時にすべてのバージョン列を自動的にチェックします。

楽観的ロックのための lock() API の唯一の目的は、更新が変更/更新されていない別のオブジェクトに依存する場合です。これにより、他のオブジェクトが変更された場合でも、トランザクションは失敗します。

これをいつ行うかは、アプリケーションとユースケースによって異なります。OPTIMISTIC は、コミット時に他のオブジェクトが更新されていないことを確認します。OPTIMISTIC_FORCE_INCREMENT は、他のオブジェクトが更新されていないことを確認し、コミット時にそのバージョンをインクリメントします。

楽観的ロックはコミット時に常に検証され、コミットするまで成功の保証はありません。flush() を使用して、事前にデータベースを強制的にロックしたり、以前のエラーをトリガーしたりできます。

于 2012-11-27T15:09:06.587 に答える
4

LockModeType.OPTIMISTICには問題があるため、またはcheck-then-actと結合したほうがよいでしょう。PESSIMISTIC_READPESSIMISTIC_WRITE

一方、LockModeType.OPTIMISTIC_FORCE_INCREMENTデータの不整合の問題はありません。通常は、子エンティティが変更されるたびに親エンティティのバージョンを制御するために使用します。

たとえば、LockModeType.OPTIMISTIC_FORCE_INCREMENTorを使用LockModeType.PESSIMISTIC_FORCE_INCREMENTして、親エンティティのバージョンが子エンティティの変更も考慮するようにすることができます。

于 2016-12-16T13:54:13.883 に答える