Spring事務管理に関するデフォルト事務間呼び出し問題
12875 ワード
事務概念略
トランザクションの伝播挙動によって、方法がデフォルトのトランザクション(REQUIRED)に設定されると、実行中にSpringがその新規のオープントランザクション(REQUIRES惽NEW)のために独立したトランザクションとして実行されるという問題があることを知っています。
もし不注意を使ったら、
具体的な原因は以下のデモの簡単な例を見てください。
部分キーコード
DemoService 1
ここで使っている事務の構成は注釈方式で、現在私達のプロジェクト開発の過程で配置ファイルの方式を使っています。普通は以下の方式です。このような方式の事務配置は問題を引き起こしやすいです。
問題が発生したコードは
また、springの事務は異常に依存していますので、demoService 2.doService()に異常が発生したら、それをロールバックと表記します。異常が出たらキャッチャーされます。demoService 1.doServiceは中に投げ出された異常を受けませんでした。
しかし、demoService 1がcomitをしているときには、トランザクションがロールバックとしてマークされていることが検出され、予期に反して
トランザクションの伝播挙動によって、方法がデフォルトのトランザクション(REQUIRED)に設定されると、実行中にSpringがその新規のオープントランザクション(REQUIRES惽NEW)のために独立したトランザクションとして実行されるという問題があることを知っています。
もし不注意を使ったら、
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
を引き起こします。具体的な原因は以下のデモの簡単な例を見てください。
部分キーコード
DemoService 1
public class DemoService1Impl implements DemoService1 {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Resource
private DemoDao demoDao;
@Resource
private DemoService2 demoService2;
/**
* , , : Exception
*/
@Override
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
public void doService() {
HashMap<String, Integer> param = new HashMap<>(2);
param.put("applId", 19);
param.put("code", 19);
demoDao.insert1(param);
try {
demoService2.doService();
} catch (Exception e) {
logger.error(" 2 ,{}", e.getMessage());
}
}
}
DemoService 2public class DemoService2Impl implements DemoService2 {
@Resource
private DemoDao demoDao;
/**
* , , : Exception
*/
@Override
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
public void doService() {
HashMap<String, Integer> param = new HashMap<>(2);
param.put("applId", 10);
param.put("code", 10);
demoDao.insert2(param);
throw new RuntimeException(" , .");
}
}
ユニットテストpublic class DemoService1ImplTest extends BaseTest {
@Resource
private DemoService1 demoService1;
@Test
public void doService() {
demoService1.doService();
}
}
説明ここで使っている事務の構成は注釈方式で、現在私達のプロジェクト開発の過程で配置ファイルの方式を使っています。普通は以下の方式です。このような方式の事務配置は問題を引き起こしやすいです。
...
...
実行結果27:38 [DEBUG] - [com.erayt.cms.cms.dao.DemoDao.insert1] prepare sql:[ insert into ...
27:38 [DEBUG] - [com.erayt.cms.cms.dao.DemoDao.insert1] prepare parameters:[19, 19] ...
27:38 [DEBUG] - {pstm-100001} Executing Statement: insert into ...
27:38 [DEBUG] - {pstm-100001} Types: [java.lang.Integer, java.lang.Integer] ...
27:38 [DEBUG] - [com.erayt.cms.cms.dao.DemoDao.insert2] prepare sql:[ insert into ...
27:38 [DEBUG] - [com.erayt.cms.cms.dao.DemoDao.insert2] prepare parameters:[10, 10] ...
27:38 [DEBUG] - {conn-100002} Preparing Statement: insert into ...
27:38 [DEBUG] - {pstm-100003} Types: [java.lang.Integer, java.lang.Integer] ...
27:38 [ERROR] - 2 , , .
27:38 [WARN ] - Caught exception while allowing TestExecutionListener ...
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit ...
at org.springframework.test.context.transaction.TransactionContext.endTransaction ...
at org.springframework.test.context.transaction.TransactionalTestExecutionListener.afterTestMethod ...
at org.springframework.test.context.TestContextManager.afterTestMethod ...
問題の分析問題が発生したコードは
try {
demoService2.doService();
} catch (Exception e) {
logger.error(" 2 ,{}", e.getMessage());
}
問題の原因は、2つのserviceの方法doServiceがデフォルトトランザクション(REQUIRED)であり、デフォルトトランザクションが呼び出された場合、外部の方法に事務がない場合、自身が新規に事務を開始します。この場合、#demoService1.doService()
のトランザクションは、新しい起動トランザクションであり、その後に呼び出しられた方法#demoService2.doService()
は、使用者#demoService1.doService()
の事務に参加します。また、springの事務は異常に依存していますので、demoService 2.doService()に異常が発生したら、それをロールバックと表記します。異常が出たらキャッチャーされます。demoService 1.doServiceは中に投げ出された異常を受けませんでした。
しかし、demoService 1がcomitをしているときには、トランザクションがロールバックとしてマークされていることが検出され、予期に反して
Unexpected
という意外なことです。UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only