2つの方法が異なるステートレスBeanに存在するシナリオを考えてみましょう
public class Bean_A {
Bean_B beanB; // Injected or whatever
public void methodA() {
Entity e1 = // get from db
e1.setName("Blah");
entityManager.persist(e1);
int age = beanB.methodB();
}
}
public class Bean_B {
//Note transaction
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void methodB() {
// complex calc to calculate age
}
}
BeanA.methodAによって開始されたトランザクションは一時停止され、BeanB.methodBで新しいトランザクションが開始されます。 methodBがmethodAによって変更された同じエンティティにアクセスする必要がある場合はどうなりますか。これによりデッドロックが発生しますが、分離レベルに依存せずにデッドロックを防ぐことはできますか?
うーん、すべてのケースをリストしましょう。
REQUIRES_NEW
は実際にはトランザクションをネストしませんが、前述のように現在のトランザクションを一時停止します。その場合、同じ情報にアクセスするトランザクションは2つだけです。 (これは、同時ではなく同じ実行スレッドにあることを除いて、2つの通常の同時トランザクションと同様です)。
T1 T2 T1 T2
― ―
| |
|
― | ―
| | |
| = | |
― | ―
|
| |
― ―
次に、楽観的対悲観的ロックを検討する必要があります。
また、ORMが運営するflushesを考慮する必要があります。 ORMでは、flush
がフレームワークによって制御されるため、書き込みが発生するタイミングを明確に制御することはできません。通常、コミットの前に1つの暗黙的なフラッシュが発生しますが、多くのエントリが変更された場合、フレームワークは中間フラッシュも実行できます。
1)読み取りはロックを取得せず、書き込みは排他的ロックを取得する楽観的ロックについて考えてみましょう。
T1による読み取りはロックを取得しません。
1a)T1が変更を時期尚早にフラッシュした場合でも、排他ロックを取得しました。 T2がコミットすると、ロックを取得しようとしますが、取得できません。 システムはブロックされています。これは特定の種類のデッドロックが原因である可能性があります。完了は、トランザクションまたはロックがどのようにタイムアウトするかによって異なります。
1b)T1が変更を時期尚早にフラッシュしなかった場合、ロックは取得されていません。 T2がコミットすると、T2はそれを取得して解放し、成功します。 T1がコミットしようとすると、競合に気づき失敗します。
2)読み取りが共有ロックを取得し、排他ロックを書き込む悲観的ロックについて考えてみましょう。
T1による読み取りは、共有ロックを取得します。
2a)T1が時期尚早にフラッシュされた場合、それはロックを排他的ロックに変えます。状況は1aと同様)
2b)T1が時期尚早にフラッシュしなかった場合、T1は共有ロックを保持します。 T2がコミットすると、排他ロックとブロックを取得しようとします。 システムは再びブロックされます。
結論:厳密に制御できない時期尚早のフラッシュが発生しない場合は、楽観的ロックで問題ありません。
エンティティを渡してマージ...
新しいエンティティをmethodB()
に渡し、それを新しいEntityManager
にマージできます。メソッドが戻ったら、エンティティを更新して変更を確認します。
_public class Bean_A {
Bean_B beanB; // Injected or whatever
public void methodA() {
Entity e1 = // get from db
e1.setName("Blah");
entityManager.persist(e1);
int age = beanB.methodB(e1);
entityManager.refresh(e1);
}
}
public class Bean_B {
//Note transaction
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void methodB(Entity e1) {
e1 = entityManager.merge(e1);
// complex calc to calculate age
}
}
_
これにより、methodB
の後に新しいトランザクションが終了したときにエンティティがコミットされることに注意してください。
...またはmethodBを呼び出す前に保存してください
上記の方法を使用する場合、エンティティはメイントランザクションとは別に保存されるため、methodB()
を呼び出す前に_Bean_A
_から保存しても、何も失われません。
_public class Bean_A {
Bean_B beanB; // Injected or whatever
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void createEntity() {
Entity e1 = // get from db
e1.setName("Blah");
entityManager.persist(e1);
}
public void methodA() {
createEntity()
int age = beanB.methodB();
}
}
public class Bean_B {
//Note transaction
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void methodB() {
// complex calc to calculate age
}
}
_
これが 最近の記事REQUIRES_NEW
トランザクション境界の使用についてです。
私の経験から、標準コードでデッドロックは発生しないはずです。制限的なwhere
句といくつかの挿入を含むクエリです。場合によっては、トランザクション中に1つのテーブルで多くの行が読み取られたり挿入されたりすると、一部のデータベースエンジンがロックエスカレーションを実行することがあります。その場合、デッドロックが発生する可能性があります。
ただし、その場合、問題はREQUIRES_NEW
からではなく、SQL設計から発生します。その設計を改善できない場合は、分離レベルをより緩いレベルに変更する他の選択肢はありません。
entityManager.persist(e1);
の後およびint age = beanB.methodB();
の前にプログラムでトランザクションをコミットすることによって?
_public class Bean_A {
Bean_B beanB; // Injected or whatever
public void methodA() {
EntityManager em = createEntityManager();
Entity e1 = // get from db
e1.setName("Blah");
entityManager.persist(e1);
em.getTransaction().commit();
int age = beanB.methodB();
}
}
public class Bean_B {
//Note transaction
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void methodB() {
// complex calc to calculate age
}
}
_
[〜#〜]編集[〜#〜]: [〜#〜] cmt [〜#〜]
CMTを使用している場合でも、プログラムでコミットできます。トランザクションはEJBContext
から取得するだけです。例: http://geertschuring.wordpress.com/2008/10/07/how-to-use-bean-managed-transactions-with-ejb3-jpa-and-jta/
または、@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void methodC()
を実行するe1.setName("Blah"); entityManager.persist(e1);
を追加できます。つまり、トランザクションでe1を永続化します。次に、methodA()
が呼び出します
_methodC();
beanB.methodB();
_