4. Spring Batch Guide - Spring Batch Job Flow

JungHwan Oh·2025년 7월 20일

Spring Batch Guide

목록 보기
4/5

앞선 시간에서 Job은 "Job을 구성하는데는 Step이 있며, 1개의 Job은 여러 개의 Step 으로 이루어져 있다." 라고 말씀해 드렸습니다.

이번 글에서는 이 Step에 대해서 그리고 Step들 간의 흐름을 어떻게 제어/관리 하는지에 대해서 알아보겠습니다.

4.1 Next

우선 예제를 한번 작성해 보겠습니다. 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 만 실행 해야 하기 때문에 아래와 같은 설정이 필요합니다.

1. application.properties 수정

# applicaiton.properties (수정)
spring.batch.job.name=${job.name:NONE}

2. VM Option 추가

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

보시는 거와 같이 step1 > step2 > step3 순서대로 step이 실행 된 것을 볼 수 있습니다.


4.2 조건별 흐름 제어 (Flow)

자 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로 실행 되었습니다.

이제 소스 설명을 해보도록 하겠습니다.

  1. .on()
  • 캐치 ExitStatus 지정
  • "*" 일 경우 모든 ExitStatus 지정
  1. to()
  • 다음으로 이동할 Step 지정
  1. from()
  • 일종의 이벤트 리스너 역할
  • 상태 값을 보고 일치하는 하는 상태라면 to()에 포함된 step 을 호출
  • step1의 이벤트 캐치가 FAILED로 되어 있는 상태에서 추가로 이벤트 캐치하러면. from 을 사용 해야한다.
  1. end()
  • FlowBuilder를 반환하는 end와 FlowBuilder를 종료하는 end 2개가 있다.
  • on("*")뒤에 있는 end는 FlowBuilder를 반환하는 end
  • build() 앞에 있는 end는 FlowBuilder를 종료하는 end
  • FlowBuilder를 반환하는 end 사용시 계속해서 from을 이어갈 수 있음

여기서 가장 중요한 포인트는 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 이 캐치하는 것도 알게 되었습니다.

4.3 Batch Status vs Exit Status

위에서 언급했지만, 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

profile
BACK_END DEVELOPER

0개의 댓글