spring batch5(spring boot3)入門 Hello World実行

# きっかけ

spring batchについて学習しようと思い、spring batch入門について調べていました。
ですが、ハローワールド実行までが難航してしまったのでここに記載します。

# 内容

spring boot2(batch4)のサポートが終了しているので、spring boot3(batch5)でハローワールドを実行しつつ、boot2との差分を比較するといった内容になっています。

# 問題

- batch4とbatch5でコードが変わる

batch4での記述がbatch5でも使えたら問題なかったのですが、一部使えなくなっていました。例：JobBuilderFactory, StepBuilderFactory

下図はboot2ではインポートできるがboot3ではインポートができないという状態です。
(JobBuilderFactoryなどはboot3では非推奨という記事は見かけましたが、使えなくなったとは記載がなかったのでIDE側が原因かもしれないです。※IDEはIntellijを使用。使えたとしても非推奨なのでコード変更の対応が必要)


spring boot3.4.0(batch5), JDK23を使用
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604560/051f87d7-dd86-2209-df06-24adf89e1f4b.png)

spring boot2.4.2(batch4), JDK14を使用
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604560/ae87d91f-4aed-5339-6502-b27cc69ddbb0.png)

# プログラムファイル

今回プログラムを記載したファイルは下記の3種になります。
- \src\main\java\com\example\demo\
  - DemoApplication.java
  - HelloTasklet.java
  - BatchConfig.java


# batch5対応の記述

## 変更前

``` java:BatchConfig.java
package com.example.demo;

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.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableBatchProcessing
public class BatchConfig {

  @Autowired
  private JobBuilderFactory jobBuilderFactory;

  @Autowired
  private StepBuilderFactory stepBuilderFactory;

  @Autowired
  private HelloTasklet helloTasklet;

  @Bean
  public Step sampleStep() {
    return stepBuilderFactory.get("HelloStep")
            .tasklet(helloTasklet)
            .build();
  }

  @Bean
  public Job sampleJob() throws Exception {
    return jobBuilderFactory.get("HelloJob")
            .incrementer(new RunIdIncrementer())
            .start(sampleStep())
            .build();
  }
}
```

## 変更後

``` java:BatchConfig.java
package com.example.demo;

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.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;

@Configuration
public class BatchConfig {

  @Autowired
  private HelloTasklet helloTasklet;

  @Bean
  public Step sampleStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
    return new StepBuilder("HelloStep", jobRepository)
            .tasklet(helloTasklet, transactionManager)
            .build();
  }

  @Bean
  public Job sampleJob(JobRepository jobRepository, PlatformTransactionManager transactionManager) throws Exception {
    return new JobBuilder("HelloJob", jobRepository)
            .incrementer(new RunIdIncrementer())
            .start(sampleStep(jobRepository, transactionManager))
            .build();
  }
}
```

## 変更内容について

### ○○BuildFactory


``` java:batch4
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
// 略
  @Bean
  public Step sampleStep() {
    return stepBuilderFactory.get("HelloStep")
            .tasklet(helloTasklet)
            .build();
  }

  @Bean
  public Job sampleJob() throws Exception {
    return jobBuilderFactory.get("HelloJob")
            .incrementer(new RunIdIncrementer())
            .start(sampleStep())
            .build();
  }
```

stepBuilderFactoryがStepBuilder, jobBuilderFacotryがJobBuilderに変わっています。
上記2種のFactoryを使用するのに@EnableBatchProcessingが必要でしたが、Factoryを使わなくなったのでこのアノテーションも削除しています。
※taskletについては後述
  ``` java:batch5
// import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
// 略
 @Bean
  public Step sampleStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
    return new StepBuilder("HelloStep", jobRepository)
            .tasklet(helloTasklet, transactionManager)
            .build();
  }

  @Bean
  public Job sampleJob(JobRepository jobRepository, PlatformTransactionManager transactionManager) throws Exception {
    return new JobBuilder("HelloJob", jobRepository)
            .incrementer(new RunIdIncrementer())
            .start(sampleStep(jobRepository, transactionManager))
            .build();
  }
```

## tasklet

``` java:HelloTasklet.java
package com.example.demo.tasklet;

import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.stereotype.Component;

@Component
@StepScope
public class HelloTasklet implements Tasklet {

  @Override
  public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
    System.out.println("Hello World");
    return RepeatStatus.FINISHED;
  }
}
```
HelloTasklet.javaはbatch4, 5でコードに変更はありません。
下記はBatchConfig.javaの内容です。

``` java:batch4
  @Bean
  public Step sampleStep() {
    return stepBuilderFactory.get("HelloStep")
            .tasklet(helloTasklet)
            .build();
  }
```

taskletにPlatformTransactionManagerが要求されるようになりました。

``` java:batch5
 @Bean
  public Step sampleStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
    return new StepBuilder("HelloStep", jobRepository)
            .tasklet(helloTasklet, transactionManager)
            .build();
  }
```

## 実行結果

``` java:DemoApplication.java
package com.example.demo;

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

@SpringBootApplication
public class DemoApplication {

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

@SpringBootApplicationが付与されているクラスを実行すると、下記内容が出力されるのを確認できると思います。
```
2024-12-12T00:24:11.148+09:00  INFO 25432 --- [demo] [           main] o.s.b.c.l.s.TaskExecutorJobLauncher      : Job: [SimpleJob: [name=HelloJob]] launched with the following parameters: [{'run.id':'{value=1, type=class java.lang.Long, identifying=true}'}]
2024-12-12T00:24:11.164+09:00  INFO 25432 --- [demo] [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [HelloStep]
Hello World
2024-12-12T00:24:11.174+09:00  INFO 25432 --- [demo] [           main] o.s.batch.core.step.AbstractStep         : Step: [HelloStep] executed in 8ms
2024-12-12T00:24:11.178+09:00  INFO 25432 --- [demo] [           main] o.s.b.c.l.s.TaskExecutorJobLauncher      : Job: [SimpleJob: [name=HelloJob]] completed with the following parameters: [{'run.id':'{value=1, type=class java.lang.Long, identifying=true}'}] and the following status: [COMPLETED] in 21ms
2024-12-12T00:24:11.181+09:00  INFO 25432 --- [demo] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
```

## 上記のようなログが出ない場合
BatchConfig.javaに@EnableBatchProcessingが付与されているとうまく実行できないみたいで、下記のようなログのみ出力され上記のようにどのバッチ、ステップが呼ばれたかなどは出力されませんでした。また、この動きに対してエラーはでませんでした。(デバッグするとjobやstepの呼び出しはしているが、taskletは呼ばれていないのが確認できます。これにずっとハマってました)

```
2024-12-12T00:31:02.411+09:00  INFO 23436 --- [demo] [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2024-12-12T00:31:02.420+09:00  INFO 23436 --- [demo] [           main] o.s.b.c.r.s.JobRepositoryFactoryBean     : No database type set, using meta data indicating: H2
2024-12-12T00:31:02.449+09:00  INFO 23436 --- [demo] [           main] .c.a.BatchObservabilityBeanPostProcessor : No Micrometer observation registry found, defaulting to ObservationRegistry.NOOP
2024-12-12T00:31:02.453+09:00  INFO 23436 --- [demo] [           main] .c.a.BatchObservabilityBeanPostProcessor : No Micrometer observation registry found, defaulting to ObservationRegistry.NOOP
2024-12-12T00:31:02.456+09:00  INFO 23436 --- [demo] [           main] o.s.b.c.l.s.TaskExecutorJobLauncher      : No TaskExecutor has been set, defaulting to synchronous executor.
2024-12-12T00:31:02.521+09:00  INFO 23436 --- [demo] [           main] com.example.demo.DemoApplication         : Started DemoApplication in 0.968 seconds (process running for 1.201)
2024-12-12T00:31:02.524+09:00  INFO 23436 --- [demo] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2024-12-12T00:31:02.546+09:00  INFO 23436 --- [demo] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
```

# gradle

``` gradle:build.gradle
plugins {
	id 'java'
	id 'org.springframework.boot' version '3.4.0'
	id 'io.spring.dependency-management' version '1.1.6'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
	toolchain {
		languageVersion = JavaLanguageVersion.of(23)
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-batch'
	runtimeOnly 'com.h2database:h2'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'org.springframework.batch:spring-batch-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

tasks.named('test') {
	useJUnitPlatform()
}
```

spring batch使うならおそらくDBが必要です。DBがないと実行時にエラーになりました。
上記だと下記がDBに該当します。
```
runtimeOnly 'com.h2database:h2'
```
runtimeOnly 'org.hsqldb:hsqldb'でもOK、他は未確認。

# 終わり