2
0

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-08-06

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.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 

以上、お疲れ様でした!

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0