
Spring Batch에서 Job은 하나 이상의 Step으로 구성됩니다. **Step은 배치 작업의 실질적인 처리 단계를 정의하는 독립적인 실행 단위입니다. 예를 들어 "파일을 읽고 유효성을 검증하는 단계", "데이터를 가공하여 DB에 저장하는 단계", "작업 완료 후 리소스를 정리하는 단계" 등이 모두 개별적인 Step으로 구현될 수 있습니다.
Step의 가장 대표적인 구현체는 TaskletStep입니다. TaskletStep은 특정 로직을 반복해서 수행하며, 이 로직을 구현하는 방식에 따라 크게 두 가지 모델로 나뉩니다.
이번 글에서는 두 모델의 특징과 사용법을 알아보겠습니다.
Step을 만드는 가장 간단한 방법은 Tasklet을 이용하는 것입니다.
Tasklet은 단일 작업을 수행하는 간단한 컴포넌트입니다. 대용량 데이터를 한 건씩 처리하는 것이 아니라, Step 내에서 한 번만 실행될 독립적인 로직을 담는 데 사용됩니다.
TaskStep에 의해 반복적으로 수행되며 반환값(RepeatStatus)에 따라 계속 수행, 종료 됩니다.
주로 다음과 같은 작업에 적합합니다.
Tasklet
@Slf4j
public class TaskletSample implements Tasklet {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
log.info("tasklet sample");
return RepeatStatus.FINISHED;
}
}
BatchConfig
@Slf4j
@RequiredArgsConstructor
@Configuration
public class BatchConfig {
private final JobRepository jobRepository;
private final PlatformTransactionManager transactionManager;
@Bean
public Job job() {
return new JobBuilder("job", jobRepository)
.start(step1())
.build();
}
@Bean
public Step step1() {
return new StepBuilder("step1", jobRepository)
.tasklet(new TaskletSample(), transactionManager)
.build();
}
}
데이터를 한 건씩 처리해서 DB에 저장하는 대신, 일정량의 묶음(Chunk) 단위로 모아서 한 번에 처리하고, 트랜잭션도 한 번만 사용하는 방식입니다.
대부분의 배치 작업은 '데이터를 읽어서(Read), 가공한 뒤(Process), 저장(Write)'하는 형태로 이루어집니다. StepBuilder는 이 패턴을 아주 효율적이고 안전하게 처리할 수 있도록 Chunk(청크) 모델을 제공합니다.
이 모델은 세 가지 주요 컴포넌트로 구성됩니다.
ItemReader<I>: 데이터 소스(파일, DB 등)에서 데이터를 한 건씩 읽어오는 역할.ItemProcessor<I, O>: ItemReader가 읽어온 데이터를 원하는 형태로 가공하는 역할. (선택사항)ItemWriter<O>: ItemProcessor가 가공한 데이터 Chunk(묶음)를 한 번에 저장하는 역할.StepBuilder를 사용하면 이 세 컴포넌트를 아주 간단하게 조립할 수 있습니다.
@Bean
public Step chunkStep(JobRepository jobRepository,
PlatformTransactionManager transactionManager,
ItemReader<Customer> customerReader,
ItemProcessor<Customer, VipCustomer> customerProcessor,
ItemWriter<VipCustomer> vipCustomerWriter) {
return new StepBuilder("chunkStep", jobRepository)
// 1. Input과 Output 타입을 지정하고, Chunk 크기를 10으로 설정
.<Customer, VipCustomer>chunk(10, transactionManager)
// 2. Reader, Processor, Writer를 각각 등록
.reader(customerReader)
.processor(customerProcessor)
.writer(vipCustomerWriter)
.build();
}
.<Customer, VipCustomer>chunk(10, ...): 이 Step은 Customer 객체를 입력받아 VipCustomer 객체를 출력하며, 10개의 아이템을 하나의 Chunk로 묶어 처리하겠다는 의미입니다. 이 Chunk 단위로 트랜잭션이 관리되어 안정성도 높아집니다.실제 운영 환경의 배치 Job은 얘기치 못한 예외가 발생해도 최대한 안정적으로 동작해야 합니다. StepBuilder는 Step의 실행 과정을 제어하고 예외 상황에 대처할 수 있는 강력한 기능들을 제공합니다.
startLimit(int)Step의 최대 실행 횟수를 지정합니다.Step이 다시 실행되면 StartLimitExceededException이 발생합니다. 기본값은 Integer.MAX_VALUE입니다.allowStartIfComplete(boolean)Job 재시작 시, 이전에 성공(COMPLETED)했던 Step을 다시 실행할지 여부를 설정합니다.false로, 성공한 Step은 건너뛰지만 true로 설정하면 항상 실행됩니다.listener(StepExecutionListener)Step의 생명주기에 맞춰 부가적인 로직을 수행하고 싶을 때 사용합니다.Step 실행 전(beforeStep), 실행 후(afterStep) 등 특정 시점에 원하는 로직(예: 로그 기록, 리소스 할당/해제)을 추가할 수 있습니다.skip이나 retry 같은 예외 처리 기능은 faultTolerant() 메서드를 먼저 호출해야 활성화됩니다.
faultTolerant()Step에 내결함성 기능을 활성화하는 관문 역할을 합니다.skip(Class<? extends Throwable> e)ItemReader나 ItemProcessor에서 특정 예외가 발생했을 때, 해당 아이템 처리를 건너뛰고 다음 아이템으로 진행하도록 설정합니다.retry(Class<? extends Throwable> e)ItemWriter 등에서 특정 예외 발생 시, Chunk 처리를 즉시 실패시키지 않고 지정된 횟수만큼 재시도하도록 설정합니다.Step을 구현하는 두 가지 방식을 살펴보았습니다. 어떤 방식을 선택할지는 처리하려는 작업의 성격에 달려 있습니다.
TaskletChunk 지향 모델이 훨씬 효율적이고 안정적입니다.