LoginSignup
92
88

まずは実践、Spring Boot Batchの動かし方

Last updated at Posted at 2020-03-24

※注意(2023/05/13追記)

Spring Boot 3.0がリリースされSpring Batchも5.0にアップデートされました。
最新の情報は公式ドキュメントを参照するようにしてください。

はじめに

Spring Batchとは、バッチアプリケーションをSpring上に構築できるフレームワークです。Spring Batchを勉強していくと登場人物が多く、概念が少し複雑です。これらを学ぶのはもちろん大事ですが、まずは動かしてみたい・・!そこで、本記事はSpring BatchをSpring Bootで動かすためにすべきことに焦点を当てます。
概念の解説は最小限とし、理論より実践、Spring Batchをどのように動かすかまずは雰囲気を感じ取ることを目標にしたいと思います。

本記事のゴール

  • Spring BootでSpring Batchをどのように書けばいいか分かる
  • Spring Batchの全体像がなんとなく分かる
  • 簡単なJob, Stepを記述できる(今回はTaskletモデルを紹介)
  • 必要な最低限のライブラリ、Java Configの記述が分かる

Spring Batchの概念

最低限頭に入れておくべき概念を紹介します。
詳細は公式リファレンスを参照してください。

全体像

Spring Batchの全体アーキテクチャです。これだけでも頭に入れておくと良いです。
ここでは以下のワードについて解説します。

  • JobLauncher
  • Job
  • JobParameter
  • Step
  • JobRepository

image.png

JobLaucher

Jobを実行するエントリーポイントとなるインタフェースです。Jobの実行はrunメソッドを呼び出すことで行われます。runメソッドは実行するJobとJobParameterを受け取ります。

public interface JobLauncher {

public JobExecution run(Job job, JobParameters jobParameters)
            throws JobExecutionAlreadyRunningException, JobRestartException,
                   JobInstanceAlreadyCompleteException, JobParametersInvalidException;
}

Job

Spring Batchで一番大本にある要素でバッチ処理全体を表します。1Job = 1バッチ処理と考えて問題ないと思います。Jobは複数のStepから構成され、1つのJobを実行するとそれを構成する複数のStepが実行されます。

JobParameter

Job実行時に渡すパラメータ。ここで渡したパラメータをJobやStepの変数として利用できます。

Step

バッチ処理を構成する最小単位の要素。Stepは大きく2種類のモデルに分類されます。

  • Chunkモデル
    • 読み込み(ItemReader)、加工(ItemProcessor)、書き込み(ItemWriter)によって構成されるステップモデル。読み込み、加工、書き込みの順でStepが構成され、必ずそれぞれの処理が実行されます(例えば読み込み、加工だけを行うようなStepにはできません)。実装者はそれぞれの処理内容を記述する必要があります。予めSpring Batch側でインタフェースが提供されているので、実装者はそれを実装してそれぞれの処理内容を書いていきます。
  • Taskletモデル
    • Chunkモデルとは違い、特に処理の流れが決まっておらず、自由に処理を記述できるステップモデル。何か単一処理を行う場合はこちらを利用します。こちらもSpring Batch側でインタフェースが提供されているので、実装者はインタフェースを実装して処理内容を書いていきます。

JobRepository

JobやStepの実行状況や実行結果を保存しておくところ。一般的にRDBなどのストレージで永続化させます。

本記事で作成するバッチアプリケーションの内容

今回はSpring Bootを起動をすると「Hello, World!」を表示するという単純なバッチアプリケーションを作成します。
ソースコードはGitHubをご覧ください。

前提

今回紹介するコードは以下のバージョンで動作確認済みです。

ライブラリ バージョン
Spring Boot 2.2.5.RELEASE
Java 11
maven 3.5.3

また、Spring Batchにおける設定はXMLではなくJava Configで記載します。

実際に動かしてみる

ここからはSpring BootでSpring Batchを動かすためにすべきことを記載していきます。

ライブラリの導入

最低限以下のライブラリを導入します。

ライブラリ 内容
spring-boot-starter-batch Spring BatchをSpring Bootで利用するためのstarterライブラリ
h2 組み込みDB
JobRepositoryを格納するために必要
pom.xml
...
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-batch</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>
        ...
    </dependencies>
...

Taskletの実装

まずStepの実処理となるTaskletを実装していきます。(今回は書き込み処理がないため、Taskletモデルを採用しています)
以下は「Hello, World!」を表示するTaskletです。
Taskletインタフェースを実装してexecuteメソッドを記述します。
executeメソッドは返り値RepeatStatus型を返す必要があります。
RepeatStatusはEnum型であり、RepeatStatus.FINISHEDRepeatStatus.CONTINUABLEの2値を持っています。
返す値によって挙動が異なるので注意してください。

RepeatStatus 返り値にした場合の挙動
FINISHED それ以上処理が続かず終了する。
nullを返した場合もFINISHEDと同様の挙動になる。
CONTINUABLE Taskletが継続して呼ばれる。
FINISHEDを返さない限りは処理が継続するので注意が必要。
HelloWorldTasklet.java
@Component
@StepScope
public class HelloWorldTasklet implements Tasklet {
    @Override
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
        System.out.println("Hello, World!");
        return RepeatStatus.FINISHED;
    }
}

JobやStepの定義

Java Configでは主にJobやStepの設定をBean定義として記述していきます。
本来はJobRepositoryやJobLauncherなどのBean定義も必要ですが、Spring Batchには@EnableBatchProcessingというアノテーションがあり、これを付与すると明示的に定義せずとも自動で設定してくれます。

BatchConfig.java
@EnableBatchProcessing
@Configuration
public class BatchConfig {

    private final JobBuilderFactory jobBuilderFactory;

    private final StepBuilderFactory stepBuilderFactory;

    private final HelloWorldTasklet helloWorldTasklet;

    public BatchConfig(JobBuilderFactory jobBuilderFactory,
                       StepBuilderFactory stepBuilderFactory,
                       HelloWorldTasklet helloWorldTasklet) {
        this.jobBuilderFactory = jobBuilderFactory;
        this.stepBuilderFactory = stepBuilderFactory;
        this.helloWorldTasklet = helloWorldTasklet;
    }

    @Bean
    public Job helloWorldJob(Step helloWorldStep) {
        return jobBuilderFactory.get("helloWorldJob") //Job名を指定
                .flow(helloWorldStep) //実行するStepを指定
                .end()
                .build();
    }

    @Bean
    public Step helloWorldStep() {
        return stepBuilderFactory.get("helloWorldStep") //Step名を指定
                .tasklet(helloWorldTasklet) //実行するTaskletを指定
                .build();
    }
}

上記コードを少し解説します。
まず、Jobについてです。jobBuilderFactory.get()メソッドの引数にはJob名を指定しています。ここでは「helloWorldJob」という名前を指定しています。次に.flow()メソッドでこのJobで実行するStepを指定しています。

次にStepですが、Jobと構成は似ており、まずstepBuilderFactory.get()メソッドの引数にStep名を指定しています。ここでは「helloWorldStep」という名前を指定しています。そして.tasklet()メソッドで実行するTaskletを指定しています。

DataSourceの設定

今回はJobRepositoryの保存先としてH2を使用するため、そのためのDataSource設定を記述します。

application.yml
spring:
  datasource:
    url: "jdbc:h2:mem:test"
    username: sa
    password:
    driver-class-name: org.h2.Driver

SpringBootApplicationの実装

これは一般的なSpring Bootの起動クラスとなんら変わらないです。

SampleSpringBatchApplication.java
@SpringBootApplication
public class SampleSpringBatchApplication {

    public static void main(String[] args) {
        SpringApplication.run(SampleSpringBatchApplication.class, args);
    }
}

設定は以上!

起動

あとはビルドして起動すればバッチが実行されます。
Spring BootではBean定義された全ジョブがデフォルトで実行される仕組みになっているため、特に設定せずとも起動時にジョブが実行されます。
以下は起動ログですが、定義したhelloWorldJobが実行されていることがわかります。

$ ./mvnw spring-boot:run

...(省略)...

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

...(省略)...

2020-03-24 23:12:36.494  INFO 76528 --- [           main] c.k.s.SampleSpringBatchApplication       : Started SampleSpringBatchApplication in 2.261 seconds (JVM running for 2.83)
2020-03-24 23:12:36.496  INFO 76528 --- [           main] o.s.b.a.b.JobLauncherCommandLineRunner   : Running default command line with: []
2020-03-24 23:12:36.568  INFO 76528 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=helloWorldJob]] launched with the following parameters: [{}]
2020-03-24 23:12:36.638  INFO 76528 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [helloWorldStep]
Hello, World!
2020-03-24 23:12:36.672  INFO 76528 --- [           main] o.s.batch.core.step.AbstractStep         : Step: [helloWorldStep] executed in 34ms
2020-03-24 23:12:36.678  INFO 76528 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=helloWorldJob]] completed with the following parameters: [{}] and the following status: [COMPLETED] in 63ms

...(省略)...

まとめ

Spring BootでSpring Batchを実行するために最低限行うことを解説しました。最後に今回行った必要なことを記載しておきます。

  • spring-boot-starter-batchの追加
  • DBの用意、DataSourceの設定
  • JobやStepなどの設定(Java Config)
  • Step処理の記述(今回はTaskletの記述)

今回はその他の概念や詳しい実行の仕組みなどの説明は割とすっ飛ばしたのでどこかでまた記事を書こうと思います。

補足

上記では触れなかったことに説明をいくつか補足で記載しておきます。

@StepScopeについて

今回実装したHelloWorldTaskletクラスには@StepScopeというアノテーションを付与しています。
このアノテーションはBeanの生成スコープを定義するもので、Step Scopeは同じStepでのみ同じインスタンスが使われるようにするスコープです。Stepが変わればインスタンスは再生成されます。デフォルトはシングルトンスコープなので、Taskletで何か状態を持っていると別のStepに引き継がれてしまうリスクがあります。それで問題なければ大丈夫ですが、問題ある場合はStep Scopeにしておくのが良いかと思います。
Step ScopeはSpring Batchでのみ利用可能です。

StepをChunkモデルで構成する場合のStepの定義方法

どこかで別記事に記載する予定です。

複数Jobを定義した場合に起動時に実行するJobを制限する方法

Spring BootでSpring Batchを起動するとデフォルトで定義した全Jobが実行されるため、実行したいJobを制限したい場合があります。
その場合はspring.batch.job.namesにJob名を指定することで実行するJobを制限することが可能です。
複数のJobを指定したい場合はカンマ区切りで指定します。

application.yml
spring:
  batch:
    job:
      names: helloWorldJob

そもそも起動時に全Jobの実行をオフにしたい場合はspring.batch.job.enabledをfalseにすることで実現できます。@SpringBootTestによるテスト時にJobの実行をさせたくない場合などに有効かと思います。サンプルのテストではspring.batch.job.enabledをfalseに設定しています。

application.yml
spring:
  batch:
    job:
      enabled: false

JobLauncherは記載していなかったけどどこで呼んでいるのか?

概念の説明のところでJobはJobLauncher#runメソッドの呼び出しによって実行されると記載しましたが、サンプルアプリケーションではJobLauncherの記載は特にしませんでした。ではどこで呼び出しているのでしょうか。

その答えはSpring BootのJobLauncherCommandLineRunnerクラスにあります。このクラスはCommandLineRunnerインタフェースを実装しているため、Spring Boot起動時にrunメソッドが呼ばれます。そのrunメソッドをたどってみると、executeメソッドでJobLauncher#runメソッドを呼び出していることが分かります。

JobLauncherCommandLineRunner.java
public class JobLauncherCommandLineRunner implements CommandLineRunner, Ordered, ApplicationEventPublisherAware {

    @Override
    public void run(String... args) throws JobExecutionException {
        logger.info("Running default command line with: " + Arrays.asList(args));
        launchJobFromProperties(StringUtils.splitArrayElementsIntoProperties(args, "="));
    }

    protected void launchJobFromProperties(Properties properties) throws JobExecutionException {
        JobParameters jobParameters = this.converter.getJobParameters(properties);
        executeLocalJobs(jobParameters);
        executeRegisteredJobs(jobParameters);
    }

    //(略)

    protected void execute(Job job, JobParameters jobParameters)
            throws JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException,
        JobParametersInvalidException, JobParametersNotFoundException {
        JobParameters parameters = getNextJobParameters(job, jobParameters);
        JobExecution execution = this.jobLauncher.run(job, parameters); //Jobの実行を呼び出している
        if (this.publisher != null) {
            this.publisher.publishEvent(new JobExecutionEvent(execution));
        }
    }

ではこのJobLauncher#runメソッドの引数に与えたJobはどこから取得しているのか。さかのぼってみるとsetJobsメソッドでJobのCollectionがセッターインジェクションされていることが分かります。つまり、Bean定義したJobをDIすることでJobを取得しています。

ちなみにapplication.ymlでspring.batch.job.namesを指定している場合はどうやってJobを取得しているのか探ってみると、BatchAutoConfigurationクラスのBeanメソッドであるjobLauncherCommandLineRunnerでJob名の取得が行われて、それをJobLauncherCommandLineRunnerクラスのフィールドにセットし、最終的にJobLauncherCommandLineRunnerのexecuteRegisteredJobsメソッドにてJob名からJobの取得を行っています。

JobLauncherCommandLineRunner.java
	private void executeRegisteredJobs(JobParameters jobParameters) throws JobExecutionException {
		if (this.jobRegistry != null && StringUtils.hasText(this.jobNames)) {
			String[] jobsToRun = this.jobNames.split(",");
			for (String jobName : jobsToRun) {
				try {
					Job job = this.jobRegistry.getJob(jobName); //Job名からJobを取得
					if (this.jobs.contains(job)) {
						continue;
					}
					execute(job, jobParameters);
				}
				catch (NoSuchJobException ex) {
					logger.debug(LogMessage.format("No job found in registry for job name: %s", jobName));
				}
			}
		}
	}

JobLauncherやJobRepositoryをカスタマイズする方法

JobLauncherやJobRepositoryの設定をカスタマイズしたい場合はBatchConfigurerインタフェースを実装してカスタマイズし、そのクラスをBean定義すればOKです。
ここでは詳しく解説しないので、公式リファレンス参照してください。

参考

92
88
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
92
88