Spring事務管理に関するデフォルト事務間呼び出し問題


事務概念略
トランザクションの伝播挙動によって、方法がデフォルトのトランザクション(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 2
public 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