Spring Batch 동작 이해 - JobBuilder

이종찬·2025년 7월 18일
post-thumbnail

Spring Boot는 어떻게 우리의 Job을 찾아 실행하는 걸까요? JobBuilder를 사용해 손쉽게 Job을 생성합니다. .start(step).next(step)처럼 간단한 코드로 복잡한 배치 작업을 정의하였습니다.

이번 글에서는 JobBuilder의 내부 동작과 Job의 실행 원리를 파헤쳐 보겠습니다.

JobLauncherApplicationRunner : Job의 자동 실행

JobLauncherApplicationRunner는 Spring Boot 애플리케이션이 실행될 때 Job을 자동으로 실행하는 역할을 합니다. (애플리케이션 시작 → 속성 확인 → Job 검색 → 결정 → 실행)

  1. spring.batch.job.enabled 속성이 true인지 확인합니다. (기본값은 true로, 배치 실행을 활성화합니다.)
  2. Application Context에 등록된 모든 Job 타입 Bean을 검색합니다.
  3. spring.batch.job.name 속성을 확인하여 실행할 Job을 결정합니다. 속성이 비어 있고 Context에 Job이 하나만 있으면 자동 선택합니다. 여러 Job이 있을 때는 이름을 명시해야 합니다.
  4. 결정된 Job을 JobLauncher를 통해 실행합니다. 이때, 에러 발생 시 JobRepository가 실패 상태를 기록하여 재시작을 지원합니다.
spring:
  batch:
    job:
      name: myJob # 실행할 Job의 이름을 명시
      enabled: true # 배치 실행 자동화 (기본값 true)

BatchProperties : Job의 유연한 설정

JobLauncherApplicationRunner가 Job을 실행할 때 필요한 다양한 설정값들은 BatchProperties 클래스를 통해 제공됩니다. spring.batch.* 네임스페이스를 사용하는 모든 설정은 이 클래스의 필드에 자동으로 바인딩됩니다.

@ConfigurationProperties("spring.batch")  
public class BatchProperties {  

    private final Job job = new Job();
    ...
}

주요 설정

  • spring.batch.job.name: 위에서 설명한 실행할 Job의 이름입니다.
  • spring.batch.jdbc.initialize-schema: 배치 관련 테이블(메타데이터 테이블)을 애플리케이션 시작 시 자동으로 생성할지 여부를 결정합니다. (always, embedded, never)
  • spring.batch.jdbc.table-prefix: 배치 테이블의 이름에 사용할 접두사를 지정합니다. (예: BATCH_)

BatchProperties를 사용함으로써 자바 코드를 변경하지 않고, 외부 설정 파일(application.yml 등)을 통해 배치 애플리케이션의 동작을 유연하게 제어할 수 있습니다.

JobBuilderHelper : JobBuilder의 공통 속성 관리

JobBuilder는 내부적으로 JobBuilderHelper를 상속받아 공통 속성을 관리합니다.
Job 이름, JobRepository, 재시작 가능 여부, JobExecutionListener 등을 처리합니다. 개발자가 직접 만질 일은 적지만, 이해하면 커스텀 Job을 만들 때 유용합니다.

Job의 구체적인 타입은 .start() 메서드에 어떤 타입의 객체를 전달하느냐에 따라 동적으로 결정됩니다.
즉, JobBuilder에서 SimpleJob인지 FlowJob인지는 시작 단계(start/flow)에서 어떤 타입의 객체(Step, Flow, Decider 등)를 넘기느냐에 따라 결정됩니다.

public class JobBuilder extends JobBuilderHelper<JobBuilder> {  
// ...

    public SimpleJobBuilder start(Step step) {  
       return new SimpleJobBuilder(this).start(step);  
    }  
  
    public JobFlowBuilder start(Flow flow) {  
       return new FlowJobBuilder(this).start(flow);  
    }
// ...
}

SimpleJob : 간단한 선형 Job

Step을 순서대로 실행하는 기본 형태입니다. .start()Step 객체를 전달하면, JobBuilder는 내부적으로 SimpleJob을 생성합니다. SimpleJob은 이름처럼 가장 단순한 형태의 Job으로, 내부적으로 List<Step>을 가지고 순서대로 Step들을 실행하는 선형적인 구조입니다. 다만, JobBuilder의 메서드(.on(), .to(), .from() 등)를 활용하면 조건부 분기(예: Step의 ExitStatus에 따른 브랜치)도 쉽게 구현할 수 있어, 단순 선형 실행뿐만 아니라 기본적인 흐름 제어도 가능합니다.

순수 선형 구조

@Bean  
public Job job(JobRepository jobRepository, Step step1, Step step2) {  
    return new JobBuilder("job", jobRepository)  
            .start(step1)  
            .next(step2)  
            .build();  
}

조건부 분기 처리 예제

    @Bean
    public Job job() {
        return new JobBuilder("simpleJob", jobRepository)
                .incrementer(new RunIdIncrementer())
                .start(step1())
                .on("COMPLETED").to(step2())
                .from(step1())
                .on("FAILED").to(step3())
                .end()
                .build();
    }

FlowJob : 복잡한 흐름 제어

.start()FlowJobExecutionDecider 객체를 전달하면, JobBuilder는 내부적으로 FlowJob을 생성합니다. FlowJob은 내부적으로 ’상태 머신(State Machine)’처럼 동작하여 복잡한 흐름 제어, 조건부 분기, 병렬 실행, 중첩 flow 등을 지원합니다. SimpleJob으로도 기본적인 분기가 가능하지만, FlowJob은 더 고급 시나리오(예: 동적 결정이나 병렬 처리)에 적합합니다.

@Bean  
public Job flowJob(JobRepository jobRepository, Flow flow, Step step5) {  
    return new JobBuilder("flowJob", jobRepository)  
            .start(flow)  
            .next(step5)  
            .end()  
            .build();  
}

Flow 객체 생성 예시

@Bean  
public Flow exampleFlow(Step stepA, Step stepB, Step stepC, JobExecutionDecider decider) {  
    return new FlowBuilder<SimpleFlow>("exampleFlow")  
            .start(stepA)  
            .next(decider)  // 동적 결정자(Decider)로 분기  
            .on("OPTION1").to(stepB)  // Decider 결과에 따라 stepB로  
            .from(decider).on("OPTION2").to(stepC)  // 또는 stepC로  
            .split(new SimpleAsyncTaskExecutor())  // 병렬 실행 예시 (stepB와 stepC 병렬)  
            .add(new FlowBuilder<SimpleFlow>("subFlow").start(stepB).build(),  
                 new FlowBuilder<SimpleFlow>("subFlow").start(stepC).build())  
            .end();  
}

정리

  1. 자동 실행: JobLauncherApplicationRunner가 애플리케이션 시작 시 Job을 자동으로 찾아 실행
  2. Job 생성: JobBuilder.start() 메서드에 전달되는 객체 타입에 따라 SimpleJob 또는 FlowJob을 동적으로 생성
  3. 흐름 제어: SimpleJob은 선형 실행과 기본적인 조건부 분기를, FlowJob은 복잡한 흐름 제어와 병렬 실행을 지원
  4. 메타데이터 관리: JobRepository를 통해 Job의 실행 상태와 메타데이터를 관리하여 재시작과 중복 실행 방지를 지원
profile
왜? 라는 질문이 사라질 때까지

0개의 댓글