Spring Batchでstepをループ

イメージとしては```step```をfor文でループしたい。これをspring-batchの```job```定義でどのように行うのか、について。

# ソースコード
## pom.xml

```xml:pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.6.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>

	<groupId>kagami</groupId>
	<artifactId>springbatchsample</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>springbatchsample</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
		<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version><!-- https://qiita.com/kagamihoge/items/fbfe90837192fd88fee9 -->
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-batch</artifactId>
		</dependency>
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.batch</groupId>
			<artifactId>spring-batch-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
</project>
```

## App
```java
package kagami.springbatchsample.deciders;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
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.job.flow.FlowExecutionStatus;
import org.springframework.batch.core.job.flow.JobExecutionDecider;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@EnableBatchProcessing
@SpringBootApplication
public class App {

  @Bean
  public Job job(JobBuilderFactory jobs, @Qualifier("s1") Step s1, JobExecutionDecider decider) {
    return jobs
        .get("myJob")
        .incrementer(new RunIdIncrementer())
        .start(s1)
        .next(decider)
          .on("COMPLETED").end()
          .on("CONTINUE").to(s1)
         .end()
        .build();
  }

  @Bean(name = "s1")
  public Step step1(StepBuilderFactory steps) {
    return steps.get("step1").tasklet((stepContribution, chunkContext) -> {
      System.out.println("step 1");
      return RepeatStatus.FINISHED;
    }).build();
  }
  
  @Bean
  public JobExecutionDecider decider() {
    return new JobExecutionDecider() {
      int count = 0;
      @Override
      public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {
        if (++count >= 5) {
          return new FlowExecutionStatus("COMPLETED");
        } else {
          return new FlowExecutionStatus("CONTINUE");
        }
      }
    };
  }

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

}
```

spring-batchには[JobExecutionDecider](https://docs.spring.io/spring-batch/trunk/reference/html/configureStep.html#programmaticFlowDecisions)を使用することで、次に実行する```step```を決定するためのロジックを実装できる。上のサンプルコードでは```count```がそのロジックに相当する。```JobExecutionDecider```は[こちら](https://www.omotenashi-mind.com/index.php?title=SpringBatch%EF%BC%9A%E3%83%AB%E3%83%BC%E3%83%97%E5%87%A6%E7%90%86%E3%82%92%E5%BF%9C%E7%94%A8%E3%81%97%E3%81%9F%E3%82%B5%E3%83%96%E3%83%95%E3%83%AD%E3%83%BC%E5%AE%9F%E7%8F%BE%E6%96%B9%E6%B3%95)を参考に実装しました。

次に、```job```定義で```JobExecutionDecider```の実装を使用する。上のサンプルコードでは、```JobExecutionDecider```の戻り値が```"COMPLETED"```であれば終了、```"CONTINUE"```であれば```step```を実行、としている。これによって、```count```の条件を満たすまで```step```が繰り返し実行される。

以下は実行時のログ。

```
2019-06-20 11:50:20.305  INFO 10084 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [step1]
step 1
2019-06-20 11:50:20.347  INFO 10084 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Duplicate step [step1] detected in execution of job=[myJob]. If either step fails, both will be executed again on restart.
2019-06-20 11:50:20.349  INFO 10084 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [step1]
step 1
2019-06-20 11:50:20.356  INFO 10084 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Duplicate step [step1] detected in execution of job=[myJob]. If either step fails, both will be executed again on restart.
2019-06-20 11:50:20.358  INFO 10084 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [step1]
step 1
2019-06-20 11:50:20.366  INFO 10084 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Duplicate step [step1] detected in execution of job=[myJob]. If either step fails, both will be executed again on restart.
2019-06-20 11:50:20.368  INFO 10084 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [step1]
step 1
2019-06-20 11:50:20.375  INFO 10084 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Duplicate step [step1] detected in execution of job=[myJob]. If either step fails, both will be executed again on restart.
2019-06-20 11:50:20.377  INFO 10084 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [step1]
step 1
```

```Duplicate step [step1] detected ...```について。ログにあるように、同一の```step```が単一の```job```で複数使われており、どちらかの```step```が失敗するとリスタート時に両方とも実行される、とある。こういう現象になるのでリスタート時には考慮してね、という意味合いと思われる。

# 参考文献
* https://www.omotenashi-mind.com/index.php?title=SpringBatch%EF%BC%9A%E3%83%AB%E3%83%BC%E3%83%97%E5%87%A6%E7%90%86%E3%82%92%E5%BF%9C%E7%94%A8%E3%81%97%E3%81%9F%E3%82%B5%E3%83%96%E3%83%95%E3%83%AD%E3%83%BC%E5%AE%9F%E7%8F%BE%E6%96%B9%E6%B3%95