Spring BatchのTasklet内でトランザクション制御を行う

はじめに

とあるバッチの更新処理がバッチ終了までデータをロックしてしまうことで 問題が発生したためループの更新箇所を1件単位でコミットすることにしました。
修正を始めるまでは、BeginTransactionして、commit、roiibackを明示的に入れればいいんだろう、
と軽く考えていましたが、思いのほかハマったので、記録しておきます。
Spring Batchのアーキテクチャについては下記がが参考になりました。
NTTデータさんのTERASOLUNAの開発ガイドライン

実装方式

Spring Batchではチャンクとタスクレットの2パターンの実装方式があります。
コミット方式も2種類あり、一括コミット方式と中間コミット方式があります。

今回はタスクレット方式で中間コミット方式を実装しました。
タスクレット方式の場合、何も制御しなければ、タスクレットのクラスを抜けたタイミングでコミットされます。(一括コミット方式)
中間コミット方式では途中でコミットする場合、自前でTransacitonManagerをInjectして手動で行います。

こちらを参考にタスクレット方式でトランザクション制御を実装しました。

 

タスクレットの実装

BatchTasklet.java

    @Autowired
    PlatformTransactionManager transactionManager;

    /**
     * チャンクサイズ。トランザクションのコミット単位.
     * application.propertiesより取得。プロパティ未設定の場合、10を指定
     */
    @Value("${app.chunk-size:10}")
    int CHUNK_SIZE;

    @Override
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
        DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
        definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        TransactionStatus status = null;

        try {
            // データを取得
            List<ReadEntity> entityList = dao.getData();
   
            // 0件の場合は処理を抜ける
            if (entityList.size() <= 0) {
                return RepeatStatus.FINISHED;
            }

            int count = 0;
            for (ReadEntity entity : entityList) {
                if (count % CHUNK_SIZE == 0) {
                    // トラン開始
                    status = transactionManager.getTransaction(definition);
                }

                // データを登録・更新
                updateInsert(entity);
                count++;

                if (count % CHUNK_SIZE == 0) {
                    // コミット
                    transactionManager.commit(status);
                    count = 0;
                }
            }

        } catch (Exception e) {
            // 例外発生時にはトランザクションをロールバック
            transactionManager.rollback(status);
            throw e;
        } finally {
            if (!status.isCompleted()) {
                // 最後のチャンクについて、トランザクションをコミットする。
                transactionManager.commit(status);
            }
        }

        return RepeatStatus.FINISHED;
    }

サンプルでは、transactionManagerは@Injectしていますが、@Autowiredにしました。
CHUNK_SIZEは固定値で定義ではなく、propertiesから取得する形にしました。

まとめ

  • Spring Batchにはタスクレット方式とチャンク方式がある。
  • コミット方式には一括コミット方式と中間コミット方式がある。
    コミットのタイミングを自前で作るため記述量は増えますが、柔軟なトランザクション制御が可能です。