앞선 시간에서 Job은 "Job을 구성하는데는 Step이 있며, 1개의 Job은 여러 개의 Step 으로 이루어져 있다." 라고 말씀해 드렸습니다.
이번 글에서는 이 Step에 대해서 그리고 Step들 간의 흐름을 어떻게 제어/관리 하는지에 대해서 알아보겠습니다.
우선 예제를 한번 작성해 보겠습니다. StepNextJobConfiguration.java 로 만들겠습니다. 이전에 배웠던 내용을 바탕으로 아래 소스와 같이 작성해주세요. (simpleJob 을 작성해 보았기 때문에 어려움은 없습니다.)
@Configuration
@Slf4j
public class StepNextJobConfiguration {
// Job 정의
@Bean
public Job stepNextJob(JobRepository jobRepository,
Step step1,
Step step2,
Step step3) {
return new JobBuilder("stepNextJob", jobRepository)
.start(step1)
.next(step2)
.next(step3)
.build();
}
// Step1 정의
@Bean
public Step step1(JobRepository jobRepository,
PlatformTransactionManager transactionManager) {
return new StepBuilder("stpe1", jobRepository)
.tasklet(((contribution, chunkContext) -> {
log.info(">>>>> This is Step1");
return RepeatStatus.FINISHED;
}), transactionManager)
.build();
}
// Step2 정의
@Bean
public Step step2(JobRepository jobRepository,
PlatformTransactionManager transactionManager) {
return new StepBuilder("stpe2", jobRepository)
.tasklet(((contribution, chunkContext) -> {
log.info(">>>>> This is Step1");
return RepeatStatus.FINISHED;
}), transactionManager)
.build();
}
// Step3 정의
@Bean
public Step step3(JobRepository jobRepository,
PlatformTransactionManager transactionManager) {
return new StepBuilder("stpe3", jobRepository)
.tasklet(((contribution, chunkContext) -> {
log.info(">>>>> This is Step1");
return RepeatStatus.FINISHED;
}), transactionManager)
.build();
}
}
여기서 stepNextJob Job을 한번 살펴 보겠습니다. 보시면 next() 라는게 있습니다. next란, 여러가지 step 들을 연결 시킬 때 사용 합니다. 즉, 해당 소스에서 next는 step1 > step2 > step3 이렇게 순차적으로 연결을 시켰습니다. 한번 실행 해 볼까요?
‼️ 저희는 기존에 simpleJob 과 이번에 추가한 stepNextJob 이렇게 2개의 Job 이 있습니다. 그냥 실행하게 된다면, 2개의 Job이 동시에 실행 될 텐데요. 저희는 stepNextJob 만 실행 해야 하기 때문에 아래와 같은 설정이 필요합니다.
# applicaiton.properties (수정)
spring.batch.job.name=${job.name:NONE}

자 이제 설정을 다 마쳤으니 실행 결과를 볼까요?

보시는 거와 같이 step1 > step2 > step3 순서대로 step이 실행 된 것을 볼 수 있습니다.
자 Next가 순차적으로 Step의 순서를 제어한다는 것을 예제를 통해서 알아봤습니다. 중요한 것은 만약 Step1 에서 오류가 발생 한다면, 뒤에 Step2, Step3 들은 정상적으로 실행 하지 못한 다는 것 입니다.
하지만 상황에 따라 정상일때는 Step B로, 오류가 났을때는 Step C로 수행해야할때가 있습니다.

이럴 경우를 대비해 Spring Batch Job에서는 조건별로 Step을 사용할 수 있습니다.
새로운 클래스 StepNextConditionalJobConfiguration 를 생성해서 살펴보겠습니다.
@Configuration
@Slf4j
public class StepNextConditionalJobConfiguration {
// Job 정의
@Bean
public Job stepNextConditionalJob(JobRepository jobRepository,
Step conditionalJobStep1, Step conditionalJobStep2, Step conditionalJobStep3) {
return new JobBuilder("stepNextConditionalJob", jobRepository)
.start(conditionalJobStep1)
.on("FAILED") // FAILED 일 경우
.to(conditionalJobStep3) // step3으로 이동한다.
.on("*") // step3의 결과 관계 없이
.end() // step3으로 이동하면 Flow가 종료한다.
.from(conditionalJobStep1) // step1로부터
.on("*") // FAILED 외에 모든 경우
.to(conditionalJobStep2) // step2로 이동한다.
.next(conditionalJobStep3) // step2가 정상 종료되면 step3으로 이동한다.
.on("*") // step3의 결과 관계 없이
.end() // step3으로 이동하면 Flow가 종료한다.
.end() // Job 종료
.build();
}
// Step1 정의
@Bean
public Step conditionalJobStep1(JobRepository jobRepository,
PlatformTransactionManager transactionManager) {
return new StepBuilder("step1", jobRepository)
.tasklet(((contribution, chunkContext) -> {
log.info("==================================================");
log.info(">>>>> This is stepNextConditionalJob Step1");
/**
* ExitStatus를 FAILED로 지정한다.
* 해당 status를 보고 flow가 진행된다.
*/
contribution.setExitStatus(ExitStatus.FAILED);
return RepeatStatus.FINISHED;
}), transactionManager)
.build();
}
// Step2 정의
@Bean
public Step conditionalJobStep2(JobRepository jobRepository,
PlatformTransactionManager transactionManager) {
return new StepBuilder("step2", jobRepository)
.tasklet(((contribution, chunkContext) -> {
log.info("==================================================");
log.info(">>>>> This is stepNextConditionalJob Step2");
return RepeatStatus.FINISHED;
}), transactionManager)
.build();
}
// Step3 정의
@Bean
public Step conditionalJobStep3(JobRepository jobRepository,
PlatformTransactionManager transactionManager) {
return new StepBuilder("step3", jobRepository)
.tasklet(((contribution, chunkContext) -> {
log.info("==================================================");
log.info(">>>>> This is stepNextConditionalJob Step3");
return RepeatStatus.FINISHED;
}), transactionManager)
.build();
}
}
위 코드의 시나리오를 살펴 보겠습니다.
시나리오1(step1 실패) : step1 -> step3
시나리오2(step2 성공) : step1 -> step2 -> step3
이렇게 전체적인 Flow 를 관리 하는 것이 위에 코드라고 볼 수 있습니다.
현재 소스상에서는 Step1의 ExitStatus 를 FAILED 로 저장 하였기 때문에 시나리오1로 실행 될 것 입니다. 아래와 같이 VM Option을 주어서 실행해 보겠습니다.


위 실행 결과를 보면 시나리오1 처럼 Step1 -> Step3로 실행 되었습니다.
이제 소스 설명을 해보도록 하겠습니다.
여기서 가장 중요한 포인트는 on 이 캐치하는 상태 값이 BathStatus 가 아닌 ExitStatus 라는 것 입니다. 그래서 분기 처리르 위해서 상태 값 조정이 필요시 우리는 ExitStatus 를 변경해야 합니다.
그렇다면, ExitStatus 상태를 변경해보겠습니다.
public Step conditionalJobStep1(JobRepository jobRepository,
PlatformTransactionManager transactionManager) {
return new StepBuilder("step1", jobRepository)
.tasklet(((contribution, chunkContext) -> {
...
// 해당 코드 주석
// contribution.setExitStatus(ExitStatus.FAILED);
...
}
기존에 작성한 ExitStatus 상태 FAIELD 처리 하는 부분을 코드를 주석 처리 하였습니다.
contribution.setExitStatus(ExitStatus.FAILED);
그렇다면, 시나리오2 처럼 step1 -> step2 -> step3 로 실행 될 것 입니다.

이렇게 실행 되는 것을 볼 수 있습니다.
이처럼 우리는 조건별로 Step 을 호출 할 수 있으며 ExitStatus 상태에 따라서 on 이 캐치하는 것도 알게 되었습니다.
위에서 언급했지만, BatchStatus 와 Exit Status 의 차이를 꼭 알아야 합니다.
Batch Status는 Job 또는 Step 실행 결과를 Spring 에서 기록하는 Enum 입니다.

하지만 우리가 위에서 작성 한 코드를 쫌 보면
.on("FAIELD").to(step2())
에서의 FAIELD 는 Step의 ExitStatus 입니다.

여기서 ExitStatus는 해석 그대로 Step의 실행 후 상태를 말하는 것 입니다.
즉, 우리는 Step의 실행 후 상태에 따라서 Step의 순서를 제어하는 것 입니다.
Development Environment
Dev Tool : IntelliJ
Spring Boot 3.5.0
Spring Batch 5.2.2
Java 17
Gradle
Reference
https://jojoldu.tistory.com/325
Source Code
https://github.com/trustonlyyou/batch-guide