배경

  • Spring Batch에서 하나의 Job이 여러 가지 조건에 따라 다른 step이 흘러가야되는 경우가 필요할 수 있다. 이를 구성할 수 있는 방법을 이해한다.

순차 실행

  • next() 메서드로 다음으로 실행할 스텝을 선언할 수 있다.
  • 만약 stepA에서 실패하면 해당 Job은 실패처리되고 stepB부터는 실행되지 않는다.
@Bean  
fun testJob(stepA: Step, stepB: Step, stepC: Step) = jobBuilder("testJob")  
    .start(stepA)  
    .next(stepB)  
    .next(stepC)  
    .build()

조건부 실행

  • on() 메서드로 스텝의 실행 결과에 따라 다음 스텝을 정할 수 있다.
  • on() 메서드는 스텝의 실행 결과인 ExitStatus 의 패턴 매칭으로 동작한다.
    • 그 중 2가지의 특수 문자가 존재한다.
      • *: 0개 이상의 문자와 일치
      • ?: 정확히 한 문자와 일치
  • on() 메서드의 매칭은 선언 순서와 관계없이, 가장 구체적인 조건부터 매칭하도록 자동으로 순서가 조절된다.
  • 만약 on() 메서드로 처리하지 않은 조건이 있다면 프레임워크에서 예외를 던지고 종료한다.
  • 흐름 중에 job을 종료시켜야 된다면, end() 메서드로 종료할 수 있다.
    • 실패처리가 필요하다면 fail()을 호출하면 된다.
@Bean  
fun testJob(stepA: Step, stepB: Step, stepC: Step) = jobBuilder("testJob")  
    .start(stepA)  
    .on("*").to(stepB)  
    .from(stepA).on("FAILED").to(stepC)  
    .end()  
    .build()

//...

@Component  
@StepScope  
class Step1Tasklet: Tasklet, StepExecutionListener {  
    override fun afterStep(stepExecution: StepExecution): ExitStatus? {  
       return ExitStatus.FAILED  
    }  
  
    override fun execute(contribution: StepContribution, chunkContext: ChunkContext): RepeatStatus? {  
       println("Step1 executed")  
       return RepeatStatus.FINISHED  
    }  
}

재시작

  • 특정 스텝까지 완료되면 STOPPED 상태로 잡을 중지시켰다가, 해당 잡을 재시작 했을 때 그 지점부터 재개하도록 구성 해야되는 상황이 필요할 수 있다.
  • stopAndRestart() 메서드를 사용하면 된다.
  • 아래 예시는 step1 이 완료되면 잡이 중지시켰다가, 재시작할 때 step2부터 실행된다.
@Bean
public Job job(JobRepository jobRepository, Step step1, Step step2) {
	return new JobBuilder("job", jobRepository)
			.start(step1).on("COMPLETED").stopAndRestart(step2)
			.end()
			.build();
}

복잡한 조건 프로그래밍 방식으로 선언하기

  • 다음에 실행할 스텝을 결정하기 위해 복잡한 분기를 가진 ExitStatus 가 필요하다면 JobExecutionDecider 인터페이스를 구현해서 사용할 수 있다.
public class MyDecider implements JobExecutionDecider {
    public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {
        String status;
        if (someCondition()) {
            status = "FAILED";
        }
        else {
            status = "COMPLETED";
        }
        return new FlowExecutionStatus(status);
    }
}
@Bean
public Job job(JobRepository jobRepository, MyDecider decider, Step step1, Step step2, Step step3) {
	return new JobBuilder("job", jobRepository)
			.start(step1)
			.next(decider).on("FAILED").to(step2)
			.from(decider).on("COMPLETED").to(step3)
			.end()
			.build();
}

Step 병렬로 실행하기

  • split() 메서드를 통해서 동시에 여러 개의 스텝을 병렬로 실행할 수 있다.
  • 아래 예시는 2개의 흐름이 동시에 실행된다.
    • 흐름1: step1 -> step2
    • 흐름2: step3
    • 두 개의 흐름이 모두 완료되면 step4가 실행되고, 두 개의 흐름 중 하나라도 실패하면 step4는 실행되지 않고 job이 종료된다.
@Bean
public Flow flow1(Step step1, Step step2) {
	return new FlowBuilder<SimpleFlow>("flow1")
			.start(step1)
			.next(step2)
			.build();
}

@Bean
public Flow flow2(Step step3) {
	return new FlowBuilder<SimpleFlow>("flow2")
			.start(step3)
			.build();
}

@Bean
public Job job(JobRepository jobRepository, Flow flow1, Flow flow2, Step step4) {
	return new JobBuilder("job", jobRepository)
				.start(flow1)
				.split(new SimpleAsyncTaskExecutor())
				.add(flow2)
				.next(step4)
				.end()
				.build();
}

일부 흐름을 분리해서 선언하기

  • Flow 를 통해 실행 흐름의 일부를 별도의 메서드나 클래스로 분리해줄 수 있다.
  • 아래 예시는 step1 -> step2 -> step3 순서로 실행된다.
@Bean
public Job job(JobRepository jobRepository, Flow flow1, Step step3) {
	return new JobBuilder("job", jobRepository)
				.start(flow1)
				.next(step3)
				.end()
				.build();
}

@Bean
public Flow flow1(Step step1, Step step2) {
	return new FlowBuilder<SimpleFlow>("flow1")
			.start(step1)
			.next(step2)
			.build();
}

참고 자료