Java + Spring Batch Tasklet + Spring Boot で Hello World

## 概要

- Spring Batch の Tasklet と Spring Boot の組み合わせでシンプルな Hello World 出力バッチプログラムを作る

## 環境

- Spring Batch 4.3.1 + Spring Boot 2.4.2
- Java 11 (AdoptOpenJDK 11.0.10+9)
- Gradle 6.8.1
- macOS Catalina

## Spring Batch の実行構成

- Job が複数の Step を呼び出す
- Step が Tasklet を呼び出す

### Job とは

[Spring Batch バッチのドメイン言語 \- リファレンス](https://spring.pleiades.io/spring-batch/docs/4.3.1/reference/html/domain.html#job)

> Spring Batch では、Job は Step インスタンスの単なるコンテナーです。フロー内で論理的に一緒に属する複数のステップを組み合わせて、再起動性など、すべてのステップに対してグローバルなプロパティの構成を可能にします。

### Step とは

[Spring Batch バッチのドメイン言語 \- リファレンス](https://spring.pleiades.io/spring-batch/docs/4.3.1/reference/html/domain.html#step)

> Step は、バッチジョブの独立したシーケンシャルフェーズをカプセル化するドメインオブジェクトです。すべてのジョブは 1 つ以上のステップで完全に構成されます。Step には、実際のバッチ処理を定義および制御するために必要なすべての情報が含まれています。

### Tasklet とは

[Spring Batch ステップの構成 \- リファレンスドキュメント](https://spring.pleiades.io/spring-batch/docs/4.3.1/reference/html/step.html#taskletStep)

> Tasklet は、execute という 1 つのメソッドを持つ単純なインターフェースです。execute は、RepeatStatus.FINISHED を返すか、例外をスローして失敗を通知するまで、TaskletStep によって繰り返し呼び出されます。

## ソースコード

### ファイル一覧

```
├── build.gradle
└── src
    └── main
        └── java
            └── com
                └── example
                    └── hello
                        ├── HelloApplication.java
                        ├── HelloConfig.java
                        └── MessageTasklet.java
```

### build.gradle

```groovy
plugins {
  id 'org.springframework.boot' version '2.4.2'
  id 'io.spring.dependency-management' version '1.0.11.RELEASE'
  id 'java'
}

group = 'com.example'
version = '0.0.1'
sourceCompatibility = '11'

repositories {
  mavenCentral()
}

dependencies {
  // Spring Batch
  implementation 'org.springframework.boot:spring-boot-starter-batch'

  // Spring Batch のメタデータを入れるデータベース
  implementation 'com.h2database:h2:1.4.200'

  // Lombok
  compileOnly 'org.projectlombok:lombok:1.18.18'
  annotationProcessor 'org.projectlombok:lombok:1.18.18'
}
```

### src/main/java/com/example/hello/HelloApplication.java

```java
package com.example.hello;

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

// Spring Boot アプリケーションクラス
@SpringBootApplication
public class HelloApplication {

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

### src/main/java/com/example/hello/HelloConfig.java

```java
package com.example.hello;

import lombok.RequiredArgsConstructor;
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

// バッチ構成クラス
@Configuration // Bean 定義クラスであることを示すアノテーション
@EnableBatchProcessing // Spring Batch を有効にする
@RequiredArgsConstructor // Lombok によるコンストラクタ自動生成
public class HelloConfig {

  private final JobBuilderFactory jobBuilderFactory;
  private final StepBuilderFactory stepBuilderFactory;

  @Bean
  public Job fooJob() {
    System.out.println("fooJob メソッドを実行");
    return jobBuilderFactory.get("myFooJob") // 一意となる任意のジョブ名を指定
      .flow(helloStep()) // 実行する Step を指定
      .end()
      .build();
  }

  @Bean
  public Job barJob() {
    System.out.println("barJob メソッドを実行");
    return jobBuilderFactory.get("myBarJob") // 一意となる任意のジョブ名を指定
      .flow(helloStep()) // 実行する Step を指定
      .next(worldStep()) // 実行する Step を指定
      .end()
      .build();
  }

  @Bean
  public Step helloStep() {
    System.out.println("helloStep メソッドを実行");
    return stepBuilderFactory.get("myHelloStep") // 任意のステップ名を指定
      .tasklet(new MessageTasklet("Hello!")) // 実行する Tasklet を指定
      .build();
  }

  @Bean
  public Step worldStep() {
    System.out.println("worldStep メソッドを実行");
    return stepBuilderFactory.get("myWorldStep") // 任意のステップ名を指定
      .tasklet(new MessageTasklet("World!")) // 実行する Tasklet を指定
      .build();
  }
}
```

### src/main/java/com/example/hello/MessageTasklet.java

```java
package com.example.hello;

import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;

// Tasklet 実装クラス
@RequiredArgsConstructor // Lombok によるコンストラクタ自動生成
public class MessageTasklet implements Tasklet {

  // 出力するメッセージ
  private final String message;

  // 具体的な処理を実装するメソッド
  @Override
  public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
    System.out.println("Message: " + message); // メッセージを出力
    return RepeatStatus.FINISHED; // 処理が終了したことを示す値を返す
  }
}
```

## ビルド

```
$ gradle build
```

## 実行

### myFooJob を実行

```
$ java -jar build/libs/hello-batch-0.0.1.jar --spring.batch.job.names=myFooJob --logging.level.root=ERROR

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

fooJob メソッドを実行
helloStep メソッドを実行
barJob メソッドを実行
worldStep メソッドを実行
Message: Hello!
```

### myBarJob を実行

```
$ java -jar build/libs/hello-batch-0.0.1.jar --spring.batch.job.names=myBarJob --logging.level.root=ERROR

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

fooJob メソッドを実行
helloStep メソッドを実行
barJob メソッドを実行
worldStep メソッドを実行
Message: Hello!
Message: World!
```

### すべてのジョブを実行

Spring Boot + Spring Batch の構成では spring.batch.job.names や spring.batch.job.enabled=false が指定されなければすべてのジョブが実行される。

```
$ java -jar build/libs/hello-batch-0.0.1.jar --logging.level.root=ERROR

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

fooJob メソッドを実行
helloStep メソッドを実行
barJob メソッドを実行
worldStep メソッドを実行
Message: Hello!
Message: Hello!
Message: World!
```

## 参考資料

- [Spring Batch \- リファレンスドキュメント](https://spring.pleiades.io/spring-batch/docs/4.3.1/reference/html/)
- [Spring Batch ステップの構成 \- リファレンスドキュメント](https://spring.pleiades.io/spring-batch/docs/4.3.1/reference/html/step.html#taskletStep)
- [Spring Batch メタデータスキーマ \- リファレンスドキュメント](https://spring.pleiades.io/spring-batch/docs/4.3.1/reference/html/schema-appendix.html)
- [2.3. Spring Batchのアーキテクチャ \- TERASOLUNA Batch Framework for Java \(5\.x\) Development Guideline](https://terasoluna-batch.github.io/guideline/5.3.0.RELEASE/ja/single_index.html#Ch02_SpringBatchArch)
- [3.3. タスクレットモデルジョブの作成 \- TERASOLUNA Batch Framework for Java \(5\.x\) Development Guideline](https://terasoluna-batch.github.io/guideline/5.3.0.RELEASE/ja/single_index.html#Ch03_CreateTaskletJob)
- [Spring Batch 4\.3\.1 API ドキュメント \- Javadoc](https://spring.pleiades.io/spring-batch/docs/4.3.1/api/)
- [Spring Boot 2\.4\.2 API ドキュメント \- Javadoc](https://spring.pleiades.io/spring-boot/docs/2.4.2/api/)
- [Spring Framework 5\.3\.3 API ドキュメント \- Javadoc](https://spring.pleiades.io/spring-framework/docs/5.3.3/javadoc-api/)
- [まずは実践、Spring Boot Batchの動かし方 \- Qiita](https://qiita.com/kawakawaryuryu/items/4b3f5cc7574b7bd6b625)