2011-07-11 15 views
4

私は、実際に実行されるのと逆の順序でデータベースにフラッシュされる2つの別々のトランザクションに問題があります。TransactionAttribute注釈(@REQUIRES_NEW)は無視されました

ここにビジネスケースがあります:RemoteJob-RemoteJobEventの1対多のリレーションがあります。新しいイベントが作成されるたびに、タイムスタンプが取得され、RemoteJobとRemoteJobEventの両方のlastModifiedフィールドに設定され、2つのレコードが保持されます(更新1回+挿入1回)。

は、ここでは、コードで次のようになります。

class Main { 

@TransactionAttribute(TransactionAttributeType.REQUIRED) 
public void mainMethod(...) { 
    RemoteJob job = remoteJobDAO.findById(...); 
    // ...   
    addEvent(job, EVENT_CODE_10); 
    // Here the separate transaction should have ended and its results 
    // permanently visible in the database. We refresh the job then 
    // to update it with the added event: 
    remoteJobDAO.refresh(job); // calls EntityManager.refresh() 
    // ... 
    boolean result = helper.addEventIfNotThere(job); 
} 

// Annotation REQUIRES_NEW here to enforce a new transaction; the 
// RemoteJobDAO.newEvent() has REQUIRED. 
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) 
public void addEvent(RemoteJob job, RemoteJobEvent event) { 
    remoteJobDAO.newEvent(job, event); 
} 

} 

class Helper { 
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) 
public boolean addEventIfNotThere(RemoteJob job) { 
    // This loads the job into the persistence context associated with a new transaction. 
    job = remoteJobDAO.findById(job.getId()); 
    // Locking the job record – this method is using as a semaphore by 2 threads, 
    // we need to make sure only one of them completes it. 
    remoteJobDAO.lockJob(job, LockModeType.WRITE); 
    // Refreshing after locking to be certain that we have current data. 
    remoteJobDAO.refresh(job); 

    // ... here comes logic for checking if EVENT_CODE_11 is not already there 
    if (/* not yet present */) { 
    remoteJobDAO.newEvent(job, EVENT_CODE_11); 
    } 

    return ...; // true - event 11 was there, false - this execution added it. 
} 

} 

まとめると:mainMethod()に我々はトランザクションコンテキストに既にあります。その後、新しいトランザクションを生成してaddEvent()のEVENT_CODE_10を作成するためにハングします。このメソッドが返ったら、その結果をすべての人にコミットして可視にする必要があります(ただし、mainMethod()のコンテキストを更新する必要があります)。最後に、addEventIfNotThere()メソッド(新しいトランザクション)に進み、EVENT_CODE_11を追加したユーザーがいないことがわかりました。その結果、2つのイベントがデータベースに存在するはずです。ここで

はトラブルだ:OpenJPAが両方イベント、追加のトランザクション否やaddEventIfNotThere()完了後よりを洗い流すようです!さらに、それは間違った順序で実行され、バージョン列の値は、最初のトランザクションがコミットされていなくても、2番目のトランザクションが前のトランザクションの情報を持っていないことを明確に示します(ログ順序、lastModifiedフィールド値そしてイベントコード): - :Apache Derbyの/ Tomcatの/ Atomikos取引Essentialsのテスト、および/ Oracleの11のWebSphere 7.0を使用してターゲット

2011-07-08T10:45:51.386 [WorkManager.DefaultWorkManager : 7] TRACE [openjpa.jdbc.SQL] - <t 2080472065, conn 1753966731> executing prepstmnt 1859546838 INSERT INTO RemoteJobEvent (id, eventCode, lastModified, version, remotejobid) VALUES (?, ?, ?, ?, ?) [params=(long) 252, (short) 11, (Timestamp) 2011-07-08 10:45:51.381, (int) 1, (long) 111] 
2011-07-08T10:45:51.390 [WorkManager.DefaultWorkManager : 7] TRACE [openjpa.jdbc.SQL] - <t 2080472065, conn 1753966731> executing prepstmnt 60425114 UPDATE RemoteJob SET lastModified = ?, version = ? WHERE id = ? AND version = ? [params=(Timestamp) 2011-07-08 10:45:51.381, (int) 3, (long) 111, (int) 2] 
2011-07-08T10:45:51.401 [WorkManager.DefaultWorkManager : 7] TRACE [openjpa.jdbc.SQL] - <t 2080472065, conn 815411354> executing prepstmnt 923940626 INSERT INTO RemoteJobEvent (id, eventCode, lastModified, version, remotejobid) VALUES (?, ?, ?, ?, ?) [params=(long) 253, (short) 10, (Timestamp) 2011-07-08 10:45:51.35, (int) 1, (long) 111] 
2011-07-08T10:45:51.403 [WorkManager.DefaultWorkManager : 7] TRACE [openjpa.jdbc.SQL] - <t 2080472065, conn 815411354> executing prepstmnt 1215645813 UPDATE RemoteJob SET lastModified = ?, version = ? WHERE id = ? AND version = ? [params=(Timestamp) 2011-07-08 10:45:51.35, (int) 3, (long) 111, (int) 2] 

これは、もちろん、OptimisticLockExceptionを生成し、それは2つの環境で同じように動作します

私の質問です:どのようにこれが可能です、その取引の境界線は尊重されていませんか?私はJPAプロバイダが内のSQL注文を自由に選択できることを理解していますが、それはの再注文全体取引はできますか?

環境についての詳細:提示されたコードは、Spring 3.0.5 JMSメッセージハンドラ(DefaultMessageListenerContainer)の一部です。 SpringはBeanの注入にも使用されますが、注釈ベースのトランザクション管理ではシステムトランザクションマネージャ(上記のWebsphere/Atomikos)を使用しているため、Springトランザクションアノテーションは使用されません。

私はこれが何らかの関心を引くことを望みます。その場合、必要に応じてもっと情報を提供します。

+0

回答それのセクション11.5.9で説明したようにAspectJを使ってSpringのAnnotationTransactionAspectを織るすることで、その後、別のものを求めるだろう。 – Will

答えて

6

私は、アノテーションベースのトランザクションサポートを担当するSpringプロキシの仕組みを読んでいないという犠牲になりました。

addEventのREQUIRES_NEWアノテーションは、同じクラス内からメソッドが呼び出されたときに無視されます。この場合、Springトランザクションプロキシは動作しないため、への呼び出しが完了した後で(長)終了するため、コードは現在のトランザクション—で完全に間違って実行されます。一方、後者のメソッドは別のクラスから呼び出されたであるため、REQUIRES_NEWは実際には別のトランザクションとして開始し、コミットします。

私はaddEvent()メソッドを別のクラスに移動し、問題は消えました。もう1つの解決方法は、<tx:annotation-driven/>の設定の仕方を変更することです。詳細はこちら:Spring Transaction Management reference

関連する問題