Spring Batchとは

## 概要
業務でSpring Batchが必要となったので勉強したことをまとめてみました
全て小文字に変換して表示するコードも書いてます

## Spring Batchとは
Spring Frameworkを中心としたSpringプロジェクトの一つ。
バッチ処理を実現するために使用するフレームワーク。

バッチ処理：あらかじめ一連の処理手順を登録しておき、自動的に処理を行う方式のこと

## Spring Batchでできること
1. バッチ処理の定期的なコミット
2. 並列、直列処理
3. 実行タイミングの指定
4. 処理結果による後続処理の分岐指定（リトライなど）

## 構成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2456624/e36952d6-c3d3-3d0d-1e9a-9550cbccc87e.png)


| 構成 | 説明 | 
| -------- | -------- |
| JobLauncher  |  Jobを起動するためのモジュール |
| Job | バッチアプリケーションの一連の処理をまとめた1実行単位、Stepを呼び出す |
| Step | Jobを構成する処理の単位、1つのJobに対して1~n個Stepを持たせることが可能 |
| Tasklet | 実際に実装される場所、①Chunkモデル ②Taskletモデル ２種の実装パターンがある |
| JobRepository | 実行履歴を管理する機能、DB上のテーブルで管理され、リトライ時などに使用される |

## 実装
### 種類
#### ①Chunkモデル
一定件数のデータごとにまとめて、入力（ItemReader）・加工（ItemProcessor）・出力（ItemWriter）の順で処理する方式

#### ②Taskletモデル
自由に処理を記述できる方式
SQLを1回発行するだけ、コマンドを発行するだけ、といった簡素なケースで使用
また複数のデータベースやファイルにアクセスしながら処理するような複雑で定型化しにくいケースでも使用

### ① Chunkモデル構成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2456624/800d4662-b11a-7548-d822-e1753c85794f.png)


| インターフェース | 説明 |
| -------- | -------- |
| ItemReader    | データの入力を担うインターフェース、データベースやファイルからJavaオブジェクトへ変換する    |
| ItemProcessor | データの加工を担うインターフェース、入力チェックやビジネスロジックを実装する |
| ItemWriter | データの出力を担うインターフェース、Javaオブジェクトからデータベースやファイルへ変換する |

## 処理全体の流れ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2456624/d7bd091a-9886-6b33-fb31-4b07c60d29a9.png)


## やってみた
### ゴール
`{"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}`
をInputとして、全て小文字に変換して表示する

### 実行構成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2456624/055aca1b-7c74-e480-40a3-2fbda1fae7f1.png)


### 1. mainクラスの作成
`@EnableBatchProcessing`アノテーションを付与とあるが、Spring Boot v3.0 以降、このアノテーションは不要になったので削除

```BatchSampleChunkApplication.java
package com.example.demo;

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

@SpringBootApplication
public class BatchSampleChunkApplication {

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

}
```

### ItemProcessorの作成
```SampleProcessor.java
package com.example.demo.chunk;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

@Component
@StepScope
@Slf4j
public class SampleProcessor implements ItemProcessor<String, String> {

	Logger logger = LoggerFactory.getLogger(SampleProcessor.class);

	@Override
	public String process(String item) throws Exception {

		// 文字列の加工(小文字に変換)
		item = item.toLowerCase();
		logger.info("Processor:{}", item);
		return item;
	}
}
```

### 2. ItemReaderの作成
```SampleReader.java
package com.example.demo.chunk;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.NonTransientResourceException;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

@Component
@StepScope
@Slf4j
public class SampleReader implements ItemReader<String> {
	Logger logger = LoggerFactory.getLogger(SampleReader.class);

	// 配列
	private String[] input = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };

	private int index = 0;

	@Override
	public String read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
		if (input.length > index) {
			// 配列の文字列を取得
			String message = input[index++];
			logger.info("Read:{}", message);

			return message;
		}
		return null;
	}
}
```

### 3. ItemWriterの作成
```SampleWritter.java
package com.example.demo.chunk;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.item.Chunk;
import org.springframework.batch.item.ItemWriter;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

@Component
@StepScope
@Slf4j
public class SampleWritter implements ItemWriter<String> {

	Logger logger = LoggerFactory.getLogger(SampleProcessor.class);

	@Override
	public void write(Chunk<? extends String> chunk) throws Exception {
		logger.info("writer:{}", chunk);
		logger.info("=========");
	}
}
```
### 4. Batch設定クラスの作成
```BatchConfig.java
package com.example.demo.config;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

@Configuration
public class BatchConfig {

	@Autowired
	private ItemReader<String> reader;
	@Autowired
	private ItemProcessor<String, String> processor;
	@Autowired
	private ItemWriter<String> writer;

	// Jobを生成
	@Bean
	public Job importUserJob(JobRepository jobRepository, Step chunkStep) {
		return new JobBuilder("importUserJob", jobRepository) // Builderの取得
				.incrementer(new RunIdIncrementer()) // IDのインクリメント
				.start(chunkStep) // 最初のStep
				.build(); // Jobの生成
	}

	@Bean
	public Step chunkStep(JobRepository jobRepository, DataSourceTransactionManager transactionManager) {
		return new StepBuilder("chunkStep", jobRepository) // Builderの取得
				.<String, String>chunk(3, transactionManager)// チャンクの設定
				.reader(reader) // readerセット
				.processor(processor) // processorセット
				.writer(writer) // writerセット
				.build(); // Stepの生成
	}

}
```

### 5. 実行
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2456624/2f3044ab-ccf0-12fb-3d3f-d7f94f7ac937.png)


<details>
    <summary style="font-weight: bold; font-size: 125%;">結果</summary>
データが読まれて、小文字に変換していることがわかる

```
2024-02-08T16:12:04.443+09:00  INFO 34882 --- [  restartedMain] c.e.demo.BatchSampleChunkApplication     : Started BatchSampleChunkApplication in 1.862 seconds (process running for 7.53)
2024-02-08T16:12:04.446+09:00  INFO 34882 --- [  restartedMain] o.s.b.a.b.JobLauncherApplicationRunner   : Running default command line with: []
2024-02-08T16:12:04.496+09:00  INFO 34882 --- [  restartedMain] o.s.b.c.l.support.SimpleJobLauncher      : Job: [SimpleJob: [name=importUserJob]] launched with the following parameters: [{'run.id':'{value=1, type=class java.lang.Long, identifying=true}'}]
2024-02-08T16:12:04.517+09:00  INFO 34882 --- [  restartedMain] o.s.batch.core.job.SimpleStepHandler     : Executing step: [chunkStep]
2024-02-08T16:12:04.535+09:00  INFO 34882 --- [  restartedMain] com.example.demo.chunk.SampleReader      : Read:Sunday
2024-02-08T16:12:04.537+09:00  INFO 34882 --- [  restartedMain] com.example.demo.chunk.SampleReader      : Read:Monday
2024-02-08T16:12:04.537+09:00  INFO 34882 --- [  restartedMain] com.example.demo.chunk.SampleReader      : Read:Tuesday
2024-02-08T16:12:04.538+09:00  INFO 34882 --- [  restartedMain] com.example.demo.chunk.SampleProcessor   : Processor:sunday
2024-02-08T16:12:04.538+09:00  INFO 34882 --- [  restartedMain] com.example.demo.chunk.SampleProcessor   : Processor:monday
2024-02-08T16:12:04.538+09:00  INFO 34882 --- [  restartedMain] com.example.demo.chunk.SampleProcessor   : Processor:tuesday
2024-02-08T16:12:04.538+09:00  INFO 34882 --- [  restartedMain] com.example.demo.chunk.SampleProcessor   : writer:[items=[sunday, monday, tuesday], skips=[]]
2024-02-08T16:12:04.540+09:00  INFO 34882 --- [  restartedMain] com.example.demo.chunk.SampleProcessor   : =========
2024-02-08T16:12:04.543+09:00  INFO 34882 --- [  restartedMain] com.example.demo.chunk.SampleReader      : Read:Wednesday
2024-02-08T16:12:04.543+09:00  INFO 34882 --- [  restartedMain] com.example.demo.chunk.SampleReader      : Read:Thursday
2024-02-08T16:12:04.543+09:00  INFO 34882 --- [  restartedMain] com.example.demo.chunk.SampleReader      : Read:Friday
2024-02-08T16:12:04.543+09:00  INFO 34882 --- [  restartedMain] com.example.demo.chunk.SampleProcessor   : Processor:wednesday
2024-02-08T16:12:04.543+09:00  INFO 34882 --- [  restartedMain] com.example.demo.chunk.SampleProcessor   : Processor:thursday
2024-02-08T16:12:04.544+09:00  INFO 34882 --- [  restartedMain] com.example.demo.chunk.SampleProcessor   : Processor:friday
2024-02-08T16:12:04.544+09:00  INFO 34882 --- [  restartedMain] com.example.demo.chunk.SampleProcessor   : writer:[items=[wednesday, thursday, friday], skips=[]]
2024-02-08T16:12:04.544+09:00  INFO 34882 --- [  restartedMain] com.example.demo.chunk.SampleProcessor   : =========
2024-02-08T16:12:04.545+09:00  INFO 34882 --- [  restartedMain] com.example.demo.chunk.SampleReader      : Read:Saturday
2024-02-08T16:12:04.545+09:00  INFO 34882 --- [  restartedMain] com.example.demo.chunk.SampleProcessor   : Processor:saturday
2024-02-08T16:12:04.545+09:00  INFO 34882 --- [  restartedMain] com.example.demo.chunk.SampleProcessor   : writer:[items=[saturday], skips=[]]
2024-02-08T16:12:04.546+09:00  INFO 34882 --- [  restartedMain] com.example.demo.chunk.SampleProcessor   : =========
2024-02-08T16:12:04.549+09:00  INFO 34882 --- [  restartedMain] o.s.batch.core.step.AbstractStep         : Step: [chunkStep] executed in 31ms
```

</details>

## 参考資料
[Spring Batchのアーキテクチャ](https://terasoluna-batch.github.io/guideline/5.0.0.RELEASE/ja/Ch02_SpringBatchArchitecture.html)
[Spring Batch入門](https://camp.trainocate.co.jp/magazine/spring-batch/)
[[公式ドキュメント] Creating a Batch Service](https://spring.io/guides/gs/batch-processing)
[勉強になったYoutube](https://www.youtube.com/watch?v=vMiSAIw3osM&t=638s)
[[公式ドキュメント] クラス JobBuilderFactory](https://spring.pleiades.io/spring-batch/docs/current/api/org/springframework/batch/core/configuration/annotation/JobBuilderFactory.html)