【SpringFramework, tasklet】Chainedトランザクションを入れ子(nested transaction)で実施する

# Spring (tasklet) でトランザクションを入れ子で実施したい！
abstract なクラスでの共通処理を外側に、個別の処理を内側に継承クラスとしてのせる想定。
色々と試行錯誤しましたが、割とシンプルにできたので残しておきます。

- 外側は＠Transactional
- 内側は手動トランザクション
- ついでに ChainedTransaction で

# 実施環境
開発PC: Windows 10
STS: 4
spring-framework: 4.2
Java: 8

# 公式ドキュメント
https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/transaction.html

# DataSource 設定 (batch.propertiesに詳細は記載する)
```xml:launch-context.xml
	<context:property-placeholder location="classpath:batch.properties" />
	<bean id="a.dataSource" class="org.apache.commons.dbcp.BasicDataSource"
		destroy-method="close">
		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url" value="${jdbc.a.url}" />
		<property name="username" value="${jdbc.a.username}" />
		<property name="password" value="${jdbc.a.password}" />

		<property name="initialSize" value="${jdbc.a.initialSize}" />
		<property name="maxActive" value="${jdbc.a.maxActive}" />
		<property name="maxIdle" value="${jdbc.a.maxIdle}" />

		<property name="testOnBorrow" value="true" />
		<property name="validationQuery" value="SELECT 1" />
	</bean>
```
b.datasource も同様に設定

# tasklet の設定
- a.b.chainedTransactionManager：
データベースa, データベースb の連結トランザクションマネージャ

```xml:module-context.xml
    <batch:job id="job-sampleJob">
        <batch:step id="sampleTasklet">
            <batch:tasklet ref="sampleTasklet" />
            <!-- デフォルトでトランザクションかける場合は↓ -->            
            <!-- <batch:tasklet transaction-manager="a.b.chainedTransactionManager"
                ref="sampleTasklet" /> -->
            <batch:listeners>
                <batch:listener ref="stepExecutionListener" />
            </batch:listeners>
        </batch:step>

        <batch:listeners>
            <batch:listener ref="jobExecutionListener" />
        </batch:listeners>
    </batch:job>
```

# tx:annotation の有効化
- launch-context の beans タグに下記追加
- xmlns:tx="http://www.springframework.org/schema/tx"
- xsi:schemaLocation="http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"

# トランザクションマネージャ設定
```xml:launch-context.xml
    <bean id="a.transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="a.dataSource" />
    </bean>
    <bean id="b.transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="b.dataSource" />
    </bean>

    <bean id="a.b.chainedTransactionManager"
          class="org.springframework.data.transaction.ChainedTransactionManager">

       <constructor-arg>
            <list>
                <ref bean="a.transactionManager"/>
                <ref bean="b.transactionManager"/>
            </list>
        </constructor-arg>
    </bean>
    <tx:annotation-driven transaction-manager="a.b.chainedTransactionManager"  />
```

# abstract な基底クラス
- 外側の基本的な処理をするクラス
- 内部の onExecute でエラーとなってもこのクラスの処理はコミットしたい

```java:AbstractTasklet.java
@Component
public abstract class AbstractTasklet implements Tasklet {

	@Autowired
	@Qualifier("a.b.chainedTransactionManager")
	protected ChainedTransactionManager abChainTranMgr;

	// tasklet 設定で transactionManager を指定した場合はココの@Transactionalは不要
	@Override
	@Transactional(rollbackFor=Exception.class, transactionManager="a.b.chainedTransactionManager")
	public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {

		RepeatStatus result = RepeatStatus.FINISHED;
		if (!onStartup(contribution, chunkContext)) {
			// startup failed
			throw new Exception("execute onStatup failed.");
		}
		try {
			result = onExecute(contribution, chunkContext);
//			throw new Exception("TEST exception.");
		}
		catch (Throwable t) {
			// error
			onTerminate(t);
			return result;
		}
		// success
		onEnd();
		return result;
	}

	// 継承クラス用
	public abstract RepeatStatus onExecute(StepContribution contribution, ChunkContext chunkContext) throws Throwable;

	// onStartup でデータベースa に変更
	// onEnd, onTerminate でデータベースa にコミット
}
```

# 継承クラス

```java:InheritedTasklet.java
@Component
public class InheritedTasklet extends AbstractTasklet {

	@Override
	public RepeatStatus onExecute(StepContribution contribution, ChunkContext chunkContext) throws Throwable {

		// Manual transaction （内部関数は @Transactional が効かない）
		DefaultTransactionDefinition def = new DefaultTransactionDefinition();
		def.setName(this.getClass().getCanonicalName() + ".onExecute");
		def.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED);

		TransactionStatus status = abChainTranMgr.getTransaction(def);
		try {
			// レコード更新
		}
		catch (Exception e) {
			abChainTranMgr.rollback(status);
			throw e;
		}
		abChainTranMgr.commit(status);

		return RepeatStatus.FINISHED;
	}
}
```

# トランザクションログの出力設定
```xml:log4j.xml
    <!-- トランザクションのログ -->
    <logger name="org.springframework.jdbc">
        <level value="DEBUG" />
    </logger>
```

# 実行してみる

```:console
[DEBUG]: org.springframework.jdbc.datasource.DataSourceTransactionManager - Creating new transaction with name [null]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
...
[DEBUG]: org.springframework.jdbc.datasource.DataSourceTransactionManager - Creating nested transaction with name [...]
...
[DEBUG]: org.springframework.jdbc.datasource.DataSourceTransactionManager - Releasing transaction savepoint
...
[DEBUG]: org.springframework.jdbc.datasource.DataSourceTransactionManager - Committing JDBC transaction on Connection
[DEBUG]: org.springframework.jdbc.datasource.DataSourceTransactionManager - Releasing JDBC Connection
```

# 外側でエラーが出るとロールバック
```:console
[DEBUG]: org.springframework.jdbc.datasource.DataSourceTransactionManager - Participating transaction failed - marking existing transaction as rollback-only
...
[DEBUG]: org.springframework.jdbc.datasource.DataSourceTransactionManager - Rolling back JDBC transaction on Connection
```

# 内側でエラーが出ても外側はコミット
```:console
[DEBUG]: org.springframework.jdbc.datasource.DataSourceTransactionManager - Rolling back transaction to savepoint
...
[DEBUG]: org.springframework.jdbc.datasource.DataSourceTransactionManager - Committing JDBC transaction on Connection 
```

以上、お疲れ様でした！