안녕하세요! 오늘은 Spring Batch의 Step 생명주기와 상태값 처리에 대해 자세히 알아보겠습니다.
Step의 생명주기 🔄
1. 기본 상태값 흐름
STARTING -> STARTED -> COMPLETED
-> FAILED
-> STOPPED
2. Step 상태값 정의
public enum BatchStatus {
COMPLETED, // 성공적 완료
STARTING, // 시작 준비
STARTED, // 실행 중
STOPPING, // 중지 진행 중
STOPPED, // 중지됨
FAILED, // 실패
ABANDONED, // 포기됨
UNKNOWN // 알 수 없음
}
Step 구현과 상태 처리 🛠
1. 기본 Step 구성
@Configuration
@RequiredArgsConstructor
public class StepConfig {
private final StepBuilderFactory stepBuilderFactory;
@Bean
public Step sampleStep() {
return stepBuilderFactory.get("sampleStep")
.<InputData, OutputData>chunk(100)
.reader(reader())
.processor(processor())
.writer(writer())
.faultTolerant()
.skipLimit(3)
.skip(Exception.class)
.listener(new StepExecutionListener() {
@Override
public void beforeStep(StepExecution stepExecution) {
stepExecution.setStatus(BatchStatus.STARTED);
}
@Override
public ExitStatus afterStep(StepExecution stepExecution) {
if (stepExecution.getStatus() == BatchStatus.COMPLETED) {
return ExitStatus.COMPLETED;
}
return ExitStatus.FAILED;
}
})
.build();
}
}
2. 상세 상태 처리
@Component
public class DetailedStepExecutionListener implements StepExecutionListener {
@Override
public void beforeStep(StepExecution stepExecution) {
// 초기 상태값 설정
stepExecution.getExecutionContext().putString("status", "INITIALIZING");
// 시작 시간 기록
stepExecution.getExecutionContext().putString("startTime",
LocalDateTime.now().toString());
}
@Override
public ExitStatus afterStep(StepExecution stepExecution) {
// 처리된 아이템 수 확인
int readCount = stepExecution.getReadCount();
int writeCount = stepExecution.getWriteCount();
int skipCount = stepExecution.getSkipCount();
// 성공 조건 검증
if (readCount == writeCount && skipCount == 0) {
return new ExitStatus("COMPLETED_PERFECT");
} else if (skipCount > 0) {
return new ExitStatus("COMPLETED_WITH_SKIPS");
}
return ExitStatus.FAILED;
}
}
주요 상태값 처리 예시 💡
1. 조건부 Step 실행
@Bean
public Job conditionalJob() {
return jobBuilderFactory.get("conditionalJob")
.start(firstStep())
.on("COMPLETED").to(successStep())
.from(firstStep())
.on("FAILED").to(recoveryStep())
.from(firstStep())
.on("*").to(defaultStep())
.end()
.build();
}
2. 커스텀 상태값 처리
public class CustomExitStatus {
public static final ExitStatus COMPLETED_WITH_WARNING =
new ExitStatus("COMPLETED_WITH_WARNING");
public static final ExitStatus NEED_RETRY =
new ExitStatus("NEED_RETRY");
}
@Component
public class CustomStepExecutionListener implements StepExecutionListener {
@Override
public ExitStatus afterStep(StepExecution stepExecution) {
if (someWarningCondition()) {
return CustomExitStatus.COMPLETED_WITH_WARNING;
} else if (needsRetry()) {
return CustomExitStatus.NEED_RETRY;
}
return ExitStatus.COMPLETED;
}
}
3. 재시도 로직
@Bean
public Step retryableStep() {
return stepBuilderFactory.get("retryableStep")
.<InputData, OutputData>chunk(10)
.reader(reader())
.processor(processor())
.writer(writer())
.faultTolerant()
.retryLimit(3)
.retry(TemporaryFailureException.class)
.listener(new RetryListener() {
@Override
public <T, E extends Throwable> boolean open(RetryContext context,
RetryCallback<T, E> callback) {
return true;
}
@Override
public <T, E extends Throwable> void close(RetryContext context,
RetryCallback<T, E> callback, Throwable throwable) {
// 재시도 완료 후 처리
}
@Override
public <T, E extends Throwable> void onError(RetryContext context,
RetryCallback<T, E> callback, Throwable throwable) {
// 에러 발생 시 처리
}
})
.build();
}
상태값 모니터링과 로깅 📝
1. Step 실행 메트릭 수집
@Component
@Slf4j
public class MetricStepExecutionListener implements StepExecutionListener {
@Override
public void beforeStep(StepExecution stepExecution) {
log.info("Step {} starting - Job Execution ID: {}",
stepExecution.getStepName(),
stepExecution.getJobExecutionId());
}
@Override
public ExitStatus afterStep(StepExecution stepExecution) {
log.info("Step {} completed with status: {}",
stepExecution.getStepName(),
stepExecution.getStatus());
log.info("Metrics - Read: {}, Written: {}, Filtered: {}, Skipped: {}",
stepExecution.getReadCount(),
stepExecution.getWriteCount(),
stepExecution.getFilterCount(),
stepExecution.getSkipCount());
return stepExecution.getExitStatus();
}
}
2. 상태값 데이터베이스 저장
@Component
@RequiredArgsConstructor
public class StepStatusPersistenceListener implements StepExecutionListener {
private final StepExecutionRepository repository;
@Override
public ExitStatus afterStep(StepExecution stepExecution) {
StepExecutionStatus status = StepExecutionStatus.builder()
.stepName(stepExecution.getStepName())
.status(stepExecution.getStatus().toString())
.readCount(stepExecution.getReadCount())
.writeCount(stepExecution.getWriteCount())
.startTime(stepExecution.getStartTime())
.endTime(stepExecution.getEndTime())
.build();
repository.save(status);
return stepExecution.getExitStatus();
}
}
실전 예시: 복잡한 상태 처리 🎯
1. 조건부 처리 파이프라인
@Configuration
public class ComplexStepConfig {
@Bean
public Step complexStep() {
return stepBuilderFactory.get("complexStep")
.<InputData, OutputData>chunk(100)
.reader(reader())
.processor(compositeProcessor())
.writer(writer())
.listener(new StepExecutionListener() {
@Override
public ExitStatus afterStep(StepExecution stepExecution) {
if (stepExecution.getSkipCount() > threshold) {
return new ExitStatus("TOO_MANY_SKIPS");
}
return stepExecution.getExitStatus();
}
})
.build();
}
@Bean
public CompositeItemProcessor<InputData, OutputData> compositeProcessor() {
List<ItemProcessor<InputData, OutputData>> delegates = Arrays.asList(
validationProcessor(),
transformationProcessor(),
enrichmentProcessor()
);
CompositeItemProcessor<InputData, OutputData> processor =
new CompositeItemProcessor<>();
processor.setDelegates(delegates);
return processor;
}
}
References 📚
Spring Batch Official Documentation - Step Processing
https://docs.spring.io/spring-batch/docs/current/reference/html/step.htmlSpring Batch - Step Execution
https://docs.spring.io/spring-batch/docs/current/reference/html/domain.html#stepExecutionBaeldung - Spring Batch Processing
https://www.baeldung.com/spring-batch-processing-logic
728x90
'300===Dev Framework > Spring Batch' 카테고리의 다른 글
Spring Tasklet - 배치 작업의 작은 영웅 🦸♂️ (0) | 2024.11.07 |
---|---|
Spring Batch 완벽 가이드 - 대용량 데이터 처리의 마법사 🪄 (1) | 2024.11.07 |
Quartz Framework Settings Explained (0) | 2024.05.29 |
Spring Batch Settings Explained (0) | 2024.05.29 |
Quartz Introduced (0) | 2024.05.27 |