Spring Batch 입문 6편. Configuring a Job — JobBuilder 의 모든 것. Step 연결·flow·restartable·Validator·Incrementer·JobExecutionListener·meta-data·운영 권장 패턴까지 풀어쓴 학습 노트.
이 글은 Spring Batch 입문에서 운영까지 시리즈 48편 중 6편이에요. 5편 에서 infrastructure 자동 구성 을 잡았다면, 이번 6편은 실제 Job 을 어떻게 정의하나 — JobBuilder 의 모든 것.
JobBuilder 의 핵심
@Bean
public Job myJob(JobRepository repo, Step step1, Step step2) {
return new JobBuilder("myJob", repo)
.start(step1)
.next(step2)
.build();
}
3편에서 본 Job 의 골격 이에요. JobBuilder 가 이 모든 옵션 을 제공해요.
기본 옵션
1. Step 연결 — start·next
.start(stepA) // 첫 Step
.next(stepB) // 그 다음
.next(stepC)
.next(stepD)
위에서 아래로 차례대로 실행돼요.
2. 조건부 flow
.start(stepA)
.on("COMPLETED").to(stepB)
.from(stepA).on("FAILED").to(errorStep)
.from(stepB).on("*").end()
.build();
각 step 의 ExitStatus (Step 종료 상태 코드) 에 따라 분기돼요. 20편 Controlling Step Flow 에서 더 깊이 다뤄요.
3. Restartable 설정
new JobBuilder("myJob", repo)
.preventRestart()
.start(step)
.build();
preventRestart() 를 박으면 실패해도 재시작이 막혀요. 어떤 Job 은 idempotency (같은 작업 두 번 실행해도 결과 동일한 성질) 가 깨질 수 있어 명시적으로 차단하는 거예요.
기본 = restartable=true.
Validator — JobParameter 검증
public class MyJobParametersValidator implements JobParametersValidator {
@Override
public void validate(JobParameters parameters) throws JobParametersInvalidException {
if (parameters.getString("targetDate") == null) {
throw new JobParametersInvalidException("targetDate is required");
}
// ...
}
}
new JobBuilder("myJob", repo)
.validator(new MyJobParametersValidator())
.start(step)
.build();
Job 이 시작되기 전에 필수 파라미터를 검증해요. 잘못 박힌 파라미터가 있으면 즉시 실패해요.
DefaultJobParametersValidator 활용
DefaultJobParametersValidator validator = new DefaultJobParametersValidator(
new String[] {"targetDate", "outputPath"}, // required
new String[] {"limit", "debug"} // optional
);
new JobBuilder(...)
.validator(validator)
.start(step)
.build();
코드를 따로 짜지 않아도 필수·옵션 파라미터를 명시 할 수 있어요.
Incrementer — 매번 새 JobParameters
3편의 함정 — 같은 JobParameters 두 번 실행 시 두 번째 skip. 매 실행마다 자동으로 다른 parameter 가 들어가게 해줘요:
new JobBuilder("myJob", repo)
.incrementer(new RunIdIncrementer())
.start(step)
.build();
RunIdIncrementer = 매 실행마다 run.id parameter +1. 항상 새 JobInstance 가 만들어져요.
또는 JobLauncher.run() 시 직접 추가:
JobParameters parameters = new JobParametersBuilder()
.addLong("run.id", System.currentTimeMillis())
.toJobParameters();
Custom Incrementer
public class DateIncrementer implements JobParametersIncrementer {
@Override
public JobParameters getNext(JobParameters parameters) {
String last = parameters.getString("targetDate");
LocalDate next = (last == null ? LocalDate.now() : LocalDate.parse(last)).plusDays(1);
return new JobParametersBuilder(parameters)
.addString("targetDate", next.toString())
.toJobParameters();
}
}
날짜 자동 +1 같은 도메인 incrementer 예요.
JobExecutionListener — Job 라이프사이클 hook
public class MyJobListener implements JobExecutionListener {
@Override
public void beforeJob(JobExecution jobExecution) {
log.info("Job starting: {}", jobExecution.getJobInstance().getJobName());
// 알림·통계 초기화 등
}
@Override
public void afterJob(JobExecution jobExecution) {
if (jobExecution.getStatus() == BatchStatus.COMPLETED) {
log.info("Job completed");
sendSuccessNotification(jobExecution);
} else {
log.error("Job failed");
sendFailureAlert(jobExecution);
}
}
}
new JobBuilder(...)
.listener(new MyJobListener())
.start(step)
.build();
자주 쓰는 자리:
- before = 통계 초기화·외부 시스템 lock·시작 알림
- after = 결과 통지·외부 시스템 unlock·정리
listener 는 여러 개 박을 수 있어요 (.listener(a).listener(b)).
Annotation 기반 Listener (대안)
@Component
public class MyJobListener {
@BeforeJob
public void before(JobExecution execution) { ... }
@AfterJob
public void after(JobExecution execution) { ... }
}
annotation 만으로 동일하게 동작해요. Bean 자동 등록.
자주 쓰는 Pattern — Job Configuration
패턴 1: Simple Linear Job
@Bean
public Job dailyReportJob(JobRepository repo,
Step extractStep, Step transformStep, Step loadStep,
JobExecutionListener listener) {
return new JobBuilder("dailyReportJob", repo)
.incrementer(new RunIdIncrementer())
.listener(listener)
.validator(new DefaultJobParametersValidator(
new String[]{"targetDate"}, new String[]{}))
.start(extractStep)
.next(transformStep)
.next(loadStep)
.build();
}
ETL (추출·변환·적재 데이터 흐름) 의 표준 형태예요.
패턴 2: Conditional Flow
@Bean
public Job dataLoadJob(JobRepository repo,
Step downloadStep, Step processStep, Step retryDownloadStep,
Step notifyFailureStep) {
return new JobBuilder("dataLoadJob", repo)
.start(downloadStep)
.on("COMPLETED").to(processStep)
.from(downloadStep).on("RETRY").to(retryDownloadStep)
.next(processStep)
.from(downloadStep).on("FAILED").to(notifyFailureStep)
.end()
.build();
}
다음 step 이 이전 결과 에 따라 갈라져요.
패턴 3: Split (병렬)
@Bean
public Job parallelJob(JobRepository repo, Step step1, Step step2, Step step3, Step mergeStep) {
Flow flow1 = new FlowBuilder<SimpleFlow>("flow1").start(step1).build();
Flow flow2 = new FlowBuilder<SimpleFlow>("flow2").start(step2).build();
Flow flow3 = new FlowBuilder<SimpleFlow>("flow3").start(step3).build();
return new JobBuilder("parallelJob", repo)
.start(flow1).split(new SimpleAsyncTaskExecutor()).add(flow2, flow3)
.next(mergeStep)
.end()
.build();
}
여러 Step 을 동시에 돌려놓고 다 끝나면 다음으로 넘어가요. 37편 scaling 에서 더 깊이 다뤄요.
Meta-Data 활용
JobExecution 의 모든 metadata = JobRepository (DB) 에 영속. 직접 조회:
@Autowired
private JobExplorer jobExplorer;
public List<JobInstance> getRecentInstances(String jobName, int count) {
return jobExplorer.findJobInstancesByJobName(jobName, 0, count);
}
public List<JobExecution> getExecutions(JobInstance instance) {
return jobExplorer.getJobExecutions(instance);
}
10편 Advanced Metadata Usage 에서 더 깊이 다뤄요.
Job 의 ExitStatus
Job 이 끝나면 ExitStatus 가 찍혀요:
new JobBuilder(...)
.listener(new JobExecutionListener() {
@Override
public void afterJob(JobExecution execution) {
execution.setExitStatus(new ExitStatus("CUSTOM_EXIT_CODE", "Description"));
}
})
.build();
기본은 COMPLETED·FAILED 같은 표준 값이에요. Custom ExitStatus 를 박으면 외부 시스템 (scheduler 등) 과 통신 할 수 있어요.
자주 헷갈리는 자리
Job 은 thread-safe 한가
Job bean 은 stateless·thread-safe. 여러 JobExecution 이 동일 Job bean 으로 동시 실행 가능. 단 같은 JobInstance 의 동시 실행 은 X (3편).
JobBuilder 의 chaining 순서
.start(step)
.next(...)
.listener(...) // listener 는 chain 어디든
.incrementer(...)
.validator(...)
.build();
대부분 순서 무관. 단 .start() 가 먼저 호출 권장 (graph 의 시작점).
Restart 동작
preventRestart() 안 박으면 기본 restartable. 실패한 JobExecution = 같은 JobInstance 의 새 JobExecution 으로 재시작 가능.
jobOperator.restart(failedExecutionId);
restartable=false 면 RestartException.
운영 권장 — Job 정의 체크리스트
- [ ]
incrementer박기 (매 실행 다른 parameter) 또는 명시 timestamp - [ ]
validator박기 (필수 parameter 검증) - [ ]
listener박기 (시작·종료 알림) - [ ] Step 들에 의미 있는 이름 (운영 로그에 표시)
- [ ] Conditional flow 가 너무 복잡하면 별도 Job 으로 분리
- [ ]
preventRestart()가 진짜 필요한 자리만 (대부분 restartable 권장)
한계·실무 함정
1. Incrementer 없이 같은 parameter
RunIdIncrementer 없으면 같은 parameter 두 번째 실행 skip. CI/CD·운영자 혼란.
2. Validator 누락
필수 parameter 없이 Job 실행 = Step 중간에 NullPointerException. validator 로 시작 전 차단.
3. Listener 안 예외
afterJob 안에서 알림 발송 같은 외부 호출 실패 = Job 자체 영향. try-catch + 알림 실패 별도 로그.
4. 너무 복잡한 Flow
.from()·.on()·.to() 가 5개+ 이상 = 가독성 폭망. 별도 Job 분리 + Scheduler 가 chaining.
5. Step 재사용 시 이름 충돌
여러 Job 이 같은 Step bean 공유 = 같은 이름. Step 의 이름이 unique* 해야 JobRepository 추적 정확. 단, 같은 step 을 다른 Job 에서 재사용은 OK (logical step).
시험 직전 한 번 더 — Configuring a Job 함정 압축 노트
- JobBuilder 기본 =
new JobBuilder(name, repo).start(step).next(step).build() .start()= 첫 Step.next()= 순차 다음 Step.on(status).to(step)= 조건부 flow (20편).from(step).on(status).to(step)= 다른 시작점 분기.split(executor).add(flow, ...)= 병렬.preventRestart()= 재시작 차단 (idempotent X 환경)- 기본 restartable = true
- Validator =
JobParametersValidator구현,validate()안 예외 DefaultJobParametersValidator(requiredKeys, optionalKeys)= 손쉬운 검증- Incrementer =
JobParametersIncrementer.getNext(prev) RunIdIncrementer=run.id자동 +1- Custom Incrementer =
DateIncrementer같이 도메인 incrementer - JobExecutionListener =
beforeJob·afterJob - 자주 쓰는 자리 — 통계·외부 lock·알림
- Annotation =
@BeforeJob·@AfterJob(대안) - Job bean = stateless·thread-safe — 여러 JobExecution 동시 실행 OK (단 같은 JobInstance X)
- 운영 권장 — incrementer + validator + listener + 의미 있는 step 이름 + flow 단순화
- 함정 — Incrementer 누락 = 같은 parameter skip
- 함정 — Validator 누락 = 중간 NPE
- 함정 — Listener 안 예외 = Job 영향, try-catch
- 함정 — 너무 복잡한 flow = 가독성 폭망 (분리 + Scheduler)
- 함정 — Step 이름 unique 권장
- ExitStatus =
COMPLETED·FAILED기본, custom 도 가능
공식 문서: Configuring a Job 에서 원문을 확인할 수 있어요.
시리즈 다른 편 (앞뒤 글 모음)
이전 글:
- 1편 — Spring Batch란 + 언제 쓰는가
- 2편 — Architecture (Application · Core · Infrastructure)
- 3편 — Domain Language (Job · JobInstance · Step · Item · Chunk)
- 4편 — v6 What's New + Hello Job 5분 hands-on
- 5편 — Batch Infrastructure (@EnableBatchProcessing · Beans)
다음 글: