Pulog

Spring BootでSpring BatchのTaskletモデルを試す

Spring BootでSpring BatchのChunkモデルを試す というタイトルの通り、Chunkモデルは例示したので、今回はTaskletモデルなバッチの実装例を紹介しようと思います。

読者想定

  • Spring BatchのChunkモデルとTaskletモデルの違いがわかっている方
  • Spring BatchのTaskletモデルでの最小限の実装例を知りたい方
  • JavaConfigを用いたSpring Batchの実装を知りたい方

公式のサンプル

前回のChunkモデルは公式のハンズオンがあったのですが、Taskletモデルには特にそのような記事が見つかりませんでした……

見つけられたら追記します。

今回作る処理

公式のサンプルが無いので、今回は単純にログを出力するだけやってみようと思います。
そのログの部分にバッチとして実行させたい処理を

Spring initializerでひな形作成

Spring Initializr

Dependenciesには以下を追加

  • Lombok
  • H2 Database
  • Spring Batch

Tasklet, Job, Stepを実装するConfigurationクラスの実装

Config用のクラスを作成(今回は BatchConfiguration クラス)する。

package com.example.demo.batchprocessing;

import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableBatchProcessing
@Slf4j
public class BatchConfiguration {

    @Autowired
    public JobBuilderFactory jobBuilderFactory;

    @Autowired
    public StepBuilderFactory stepBuilderFactory;

    /**
     * 1つのバッチ処理の処理順やエラーハンドリング等の定義を行う
     *
     * @return Jobを返却
     */
    @Bean
    public Job importUserJob() {
        return jobBuilderFactory.get("importUserJob")
                .incrementer(new RunIdIncrementer())
                .flow(step1())
                .end()
                .build();
    }

    /**
     * Jobの中に含まれるステップを定義する
     * バッチ処理もラムダ式内に記述してしまうか、別クラスに分けて書くことも可能
     *
     * @return ステップを返却する
     */
    @Bean
    public Step step1() {
        return stepBuilderFactory.get("step1")
                .tasklet((contribution, chunkContext) -> {
                    log.info("tasklet step");
                    log.info("ここにバッチ処理を実装する");
                    return RepeatStatus.FINISHED;
                })
                .build();
    }

}

Batch終了後にSpringApplicationも終了する

これで実装は完成したが、Batch処理が完了してもビルドタスクが止まらないことに気づくと思います。

そこで、main関数でSpringApplicationを呼んでいる箇所に以下関数でくくってあげることで、Batch処理が終了した際に自動的にSpringApplicationも終了してくれるようになります。

package com.example.batchDemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class BatchDemoApplication {

	public static void main(String[] args) {
-		SpringApplication.run(BatchDemoApplication.class, args);
+		System.exit(SpringApplication.exit(SpringApplication.run(BatchDemoApplication.class, args)));
	}

}

バッチの実行

ここまで作成し、Spring Bootを起動したタイミングでバッチが走るようになる。

Gradleプロジェクトであれば、 bootRun タスクでSpring Bootが立ち上がる。

10:18:37: Executing task 'DemoApplication.main()'...

> Task :compileJava UP-TO-DATE
> Task :processResources
> Task :classes

> Task :DemoApplication.main()

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.5.RELEASE)

2020-11-20 10:18:39.663  INFO 10291 --- [           main] com.example.demo.DemoApplication         : Starting DemoApplication on YukinoMacBook.local with PID 10291 (/Users/yuki/demo/build/classes/java/main started by yuki in /Users/yuki/demo)
2020-11-20 10:18:39.667  INFO 10291 --- [           main] com.example.demo.DemoApplication         : No active profile set, falling back to default profiles: default
2020-11-20 10:18:40.901  INFO 10291 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2020-11-20 10:18:41.271  INFO 10291 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2020-11-20 10:18:41.426  INFO 10291 --- [           main] o.s.b.c.r.s.JobRepositoryFactoryBean     : No database type set, using meta data indicating: H2
2020-11-20 10:18:41.761  INFO 10291 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : No TaskExecutor has been set, defaulting to synchronous executor.
2020-11-20 10:18:41.911  INFO 10291 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 3.147 seconds (JVM running for 3.898)
2020-11-20 10:18:41.915  INFO 10291 --- [           main] o.s.b.a.b.JobLauncherApplicationRunner   : Running default command line with: []
2020-11-20 10:18:42.006  INFO 10291 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=importUserJob]] launched with the following parameters: [{run.id=1}]
2020-11-20 10:18:42.082  INFO 10291 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [step1]
2020-11-20 10:18:42.095  INFO 10291 --- [           main] c.e.d.b.BatchConfiguration               : tasklet step
2020-11-20 10:18:42.095  INFO 10291 --- [           main] c.e.d.b.BatchConfiguration               : ここにバッチ処理を実装する
2020-11-20 10:18:42.107  INFO 10291 --- [           main] o.s.batch.core.step.AbstractStep         : Step: [step1] executed in 24ms
2020-11-20 10:18:42.118  INFO 10291 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=importUserJob]] completed with the following parameters: [{run.id=1}] and the following status: [COMPLETED] in 62ms
2020-11-20 10:18:42.121  INFO 10291 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2020-11-20 10:18:42.129  INFO 10291 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

BUILD SUCCESSFUL in 4s
3 actionable tasks: 2 executed, 1 up-to-date
10:18:42: Task execution finished 'DemoApplication.main()'.

終わりに

ここまでできたら実際にJARにまとめるなどして、実際にバッチとして使い始めることができます。

Taskletモデル自体は実装は自由にできてしまうので、実装は比較的簡単ですが、それなりのデメリット(中間コミットの概念が基本的には無いなど)もあるうえ、Taskletのみ使うならそもそもSpring Batch使う恩恵があまり無い気がするので、実装しようとしているバッチがChunkモデルで実装できないか吟味した上で適宜Taskletモデルな実装をする方針が良いと思います。

次回のSpring Batchの記事ではもう少し踏み込んだユースケースを紹介しようと思います。

それでわ。

参考にした記事・資料