스프링 배치 가이드
- 지난 포스팅에서 튜토리얼 보고 따라해봤는데, 이번에는 개념부터 천천히 공부해보자.
- 책을 보고 공부하려 했으나, 스프링과 부트의 배치 사용 문법이 많이 달라 이동욱님의 블로그를 보고 공부했다.
http://devhooney.tistory.com/135
1. Next
- next()는 순차적으로 step을 연결할 때 사용된다.
- 코드를 작성한다.
@Slf4j
@Configuration
@RequiredArgsConstructor
public class StepNextJobConfiguration {
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
@Bean
public Job stepNextJob() {
return jobBuilderFactory.get("stepNextJob")
.start(step1())
.next(step2())
.next(step3())
.build();
}
@Bean
public Step step1() {
return stepBuilderFactory.get("step1")
.tasklet((contribution, chunkContext) -> {
log.info("Step1!!!!!!");
return RepeatStatus.FINISHED;
})
.build();
}
@Bean
public Step step2() {
return stepBuilderFactory.get("step2")
.tasklet((contribution, chunkContext) -> {
log.info("Step2!!!!!!");
return RepeatStatus.FINISHED;
})
.build();
}
@Bean
public Step step3() {
return stepBuilderFactory.get("step3")
.tasklet((contribution, chunkContext) -> {
log.info("Step3!!!!!!");
return RepeatStatus.FINISHED;
})
.build();
}
}
- 구성편집에서 프로그램인수(Program argument)를 requestDate=20221211를 version=1로 변경해준다.
- 그리고 실행하면 에러가 나는데, 지난번 포스팅때 만든 simpleJob에서 parameter로 requestDate를 받고있기 때문.
- 그래서 방금 작성한 배치만 실행되도록 application.yml 파일을 수정해준다.
spring:
profiles:
active: local
batch: # 추가된 설정
job:
names: ${job.name:NONE}
- 이 설정은 스프링 배치가 실행될 때, 프로그램 인수로 job.name 값이 넘어오면, 해당 값과 일치하는 job만 실행하게 해준다.
- 값이 NONE으로 되어 있는데, 이건 job.name이 없을 경우에 NONE으로 처리하고, NONE으로 처리된 배치는 실행되지 않는다.
- 구성편집에서 프로그램 인수를 --job.name=stepNextJob version=2로 수정한다. 1은 전에 실행했기때문에 값을 2로 변경해줬다.
- 이렇게 하고 실행하면 잘된다.
2. Flow (조건 별 흐름 제어)
- Next가 순차적으로 Step의 순서를 제어하는데, step1 에러가 발생하면 step2, 3은 실행되지 않는다.
- 분기처리가 필요할 때가 있다.
- 코드를 작성한다.
@Slf4j
@Configuration
@RequiredArgsConstructor
public class StepNextConditionalJobConfiguration {
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
@Bean
public Job stepNextConditionalJob() {
return jobBuilderFactory.get("stepNextConditionalJob")
.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();
}
@Bean
public Step conditionalJobStep1() {
return stepBuilderFactory.get("step1")
.tasklet((contribution, chunkContext) -> {
log.info("Step1~~~~");
/**
ExitStatus를 FAILED로 지정한다.
해당 status를 보고 flow가 진행된다.
**/
contribution.setExitStatus(ExitStatus.FAILED);
return RepeatStatus.FINISHED;
})
.build();
}
@Bean
public Step conditionalJobStep2() {
return stepBuilderFactory.get("conditionalJobStep2")
.tasklet((contribution, chunkContext) -> {
log.info("Step2~~~~");
return RepeatStatus.FINISHED;
})
.build();
}
@Bean
public Step conditionalJobStep3() {
return stepBuilderFactory.get("conditionalJobStep3")
.tasklet((contribution, chunkContext) -> {
log.info("Step3~~~~");
return RepeatStatus.FINISHED;
})
.build();
}
}
- 위 코드는 step1이 성공하면 순차적으로 step2, 3을 진행하고, 실패하면 step1에서 step3으로 넘어간다.
- 전체 Flow를 관리하는 코드를 보면
@Bean
public Job stepNextConditionalJob() {
return jobBuilderFactory.get("stepNextConditionalJob")
.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();
}
- on():
- 캐치할 ExitStatus 지정.
- *일 경우 모든 ExitStatus
- 분기처리가 필요할 경우 ExitStatus를 조정해야한다.
- to(): 다음으로 이동할 step 지정
- from():
- 상태값을 보고 일치하는 상태일 경우 to()에 포함돤 step 호출
- step1의 이벤트 캐치가 FAILED일 경우에 추가로 캐치를 하려면 from()을 써야한다.
- end():
- end()는 FlowBuilder를 반환하는 end, 종료하는 end 2개가 있다.
- on("*") 뒤에 있는 end는 FlowBuilder를 반환하는 end
- build() 앞에 있는 end는 FlowBuilder를 종료하는 end
- FlowBuilder를 반환하는 end 사용시 계속해서 from을 이어갈 수 있다.
- step1 코드를 보면
@Bean
public Step conditionalJobStep1() {
return stepBuilderFactory.get("step1")
.tasklet((contribution, chunkContext) -> {
log.info("Step1~~~~");
/**
ExitStatus를 FAILED로 지정한다.
해당 status를 보고 flow가 진행된다.
**/
contribution.setExitStatus(ExitStatus.FAILED);
return RepeatStatus.FINISHED;
})
.build();
}
- contribution.setExitStatus()로 상태를 지정해줬다.
- 강제로 FAILED를 지정했기 때문에 실행하면 step2는 실행이 되지 않는다.
3. BatchStatus와 ExitStatus의 차이
- BatchStatus는 Job 또는 Step의 실행 결과를 Spring에서 기록할 때 사용하는 Enum이다.
- BatchStatus로 사용 되는 값은 COMPLETED, STARTING, STARTED, STOPPING, STOPPED, FAILED, ABANDONED, UNKNOWN이 있다.
.on("FAILED").to(stepB())
- 위 코드에서 on 메소드가 참조하는 것은 BatchStatus 으로 생각할 수 있지만 실제 참조되는 값은 Step의 ExitStatus이다.
- ExitStatus는 Step의 실행 후 상태를 말한다.(ExitStatus는 Enum이 아니다.)
4. Decide
- Decide는 Step들의 Flow속에서 분기만 담당한다.
- 코드를 작성한다.
@Slf4j
@Configuration
@RequiredArgsConstructor
class DeciderJobConfiguration {
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
@Bean
public Job deciderJob() {
return jobBuilderFactory.get("deciderJob")
.start(startStep())
.next(decider()) // 홀수 | 짝수 구분
.from(decider()) // decider의 상태가
.on("ODD") // ODD라면
.to(oddStep()) // oddStep로 간다.
.from(decider()) // decider의 상태가
.on("EVEN") // EVENㅇㅣ라면
.to(evenStep()) // evenStep로 간다.
.end() // builder 종료
.build();
}
@Bean
public Step startStep() {
return stepBuilderFactory.get("startStep")
.tasklet((contribution, chunkContext) -> {
log.info(">>>>> Start!");
return RepeatStatus.FINISHED;
})
.build();
}
@Bean
public Step evenStep() {
return stepBuilderFactory.get("evenStep")
.tasklet((contribution, chunkContext) -> {
log.info(">>>>> 짝수입니다.");
return RepeatStatus.FINISHED;
})
.build();
}
@Bean
public Step oddStep() {
return stepBuilderFactory.get("oddStep")
.tasklet((contribution, chunkContext) -> {
log.info(">>>>> 홀수입니다.");
return RepeatStatus.FINISHED;
})
.build();
}
@Bean
public JobExecutionDecider decider() {
return new OddDecider();
}
public static class OddDecider implements JobExecutionDecider {
@Override
public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {
Random rand = new Random();
int randomNumber = rand.nextInt(50) + 1;
log.info("랜덤숫자: {}", randomNumber);
if(randomNumber % 2 == 0) {
return new FlowExecutionStatus("EVEN");
} else {
return new FlowExecutionStatus("ODD");
}
}
}
}
- 위 코드의 Flow는 startStep -> oddDecider에서 홀수 인지 짝수인지 구분 -> oddStep or evenStep 진행
- decider를 Flow 사이에 넣는 로직은 아래 코드
@Bean
public Job deciderJob() {
return jobBuilderFactory.get("deciderJob")
.start(startStep())
.next(decider()) // 홀수 | 짝수 구분
.from(decider()) // decider의 상태가
.on("ODD") // ODD라면
.to(oddStep()) // oddStep로 간다.
.from(decider()) // decider의 상태가
.on("EVEN") // EVEN이라면
.to(evenStep()) // evenStep로 간다.
.end() // builder 종료
.build();
}
- start(): Job Flow의 첫 번째 Step을 시작한다.
- next(): start() 이후 decider를 실행한다.
- from(): decider의 상태값을 보고 일치하면 to()에 포함된 step을 호출한다.
- 분기 로직에 대한 모든 일은 OddDecider가 하고 있다.
public static class OddDecider implements JobExecutionDecider {
@Override
public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {
Random rand = new Random();
int randomNumber = rand.nextInt(50) + 1;
log.info("랜덤숫자: {}", randomNumber);
if(randomNumber % 2 == 0) {
return new FlowExecutionStatus("EVEN");
} else {
return new FlowExecutionStatus("ODD");
}
}
}
- OddDecider는 Step이 아니기 때문에 ExitStatus가 아닌 FlowExecutionStatus로 상태를 관리한다
- EVEN, ODD라는 상태를 생성하여 반환하였고, 이를 from().on() 에서 사용한다.
- 실행해보면
- 잘 되는걸 볼 수 있다.
- 참고
'개발 > Java&Kotlin' 카테고리의 다른 글
[Spring] 스프링 배치(Spring Batch) 가이드 따라가기 (4) (0) | 2022.12.14 |
---|---|
[Spring] 스프링 배치(Spring Batch) 가이드 따라가기 (3) (0) | 2022.12.13 |
[Spring] 스프링 배치(Spring Batch) 가이드 따라가기 (1) (0) | 2022.12.10 |
[Spring] 스프링 배치(Spring Batch) 맛보기 (1) | 2022.12.06 |
[Kotlin] 코틀린 + 스프링부트 + JPA 사용해보기 (0) | 2022.12.02 |