Spring (tasklet) でトランザクションを入れ子で実施したい!
abstract なクラスでの共通処理を外側に、個別の処理を内側に継承クラスとしてのせる想定。
色々と試行錯誤しましたが、割とシンプルにできたので残しておきます。
- 外側は@Transactional
- 内側は手動トランザクション
- ついでに ChainedTransaction で
実施環境
開発PC: Windows 10
STS: 4
spring-framework: 4.2
Java: 8
公式ドキュメント
DataSource 設定 (batch.propertiesに詳細は記載する)
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 の連結トランザクションマネージャ
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"
トランザクションマネージャ設定
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 でエラーとなってもこのクラスの処理はコミットしたい
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 にコミット
}
継承クラス
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;
}
}
トランザクションログの出力設定
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
以上、お疲れ様でした!