반응형
Spring-Batch의 기본 개념을 정리하고, 간단한 실습 환경부터 연습해보는 포스팅입니다
참고
Spring-Batch 공식문서(4.3.3 기준)
https://docs.spring.io/spring-batch/docs/4.3.3/reference/html/
이동욱님의 블로그
https://jojoldu.tistory.com/324?category=902551
1. Next method
- 순차적으로 Step을 실행시킬 때 사용하는 메서드입니다
- 예제 코드
// Job @Slf4j @Configuration @RequiredArgsConstructor public class SimpleNextJobConfiguration { 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(); } }
- "stepNextJob"이라는 jobName을 가진 Job아래 "step1", "step1", "step1" 순차적으로 Step을 실행 시킵니다
- 위에서 새로 정의한 "stepNextJob" 만 실행시키려면 추가 셋팅을 해줘야합니다
- 특정 Job만 실행 시키기
- application.yml에 config 정의
spring.batch.job.names: ${job.name:NONE}
- Spring Batch가 실행될때, Program arguments로 job.name 값이 넘어오면 해당 값과 일치하는 Job만 실행
- job.name 값이 있으면 할당하고 없다면 NONE(아무런 배치도 실행하지 않음)을 할당합니다
- program arguments에 job.name 추가
--job.name=stepNextJob
- application.yml에 config 정의
- 실제 운영 환경에서는
와 같이 jvm 옵션을 통해 실행시킵니다java -jar batch-application.jar --job.name=simpleJob
- 특정 Job만 실행 시키기
- 참고로 위와 같이 JobParameters 없이 정의 Step들은 다시 실행시키면 Exception이 발생하는 것이 아닌 EXIT_CODE가 NOOP인 "All steps already completed or no steps configured for this job" msg가 나온다
( 실행은 안된다 )
2. 조건별 흐름 제어
- 위에서 본 Next로 흐름 제어는 앞 선 Step에서 오류가 나면 중단, 정상적으로 실행됐을때만 다음 Step을 수행했습니다
- 이와 다르게 Step의 특정 조건 별로 분기를 태울 수 있습니다
- 예제 코드
// 조건별 분기 @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") // conditionalJobStep1이 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(">>>>> This is stepNextConditionalJob 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(">>>>> This is stepNextConditionalJob Step2"); return RepeatStatus.FINISHED; }) .build(); } @Bean public Step conditionalJobStep3() { return stepBuilderFactory.get("conditionalJobStep3") .tasklet((contribution, chunkContext) -> { log.info(">>>>> This is stepNextConditionalJob Step3"); return RepeatStatus.FINISHED; }) .build(); } }
- step1 실패 시나리오: step1 -> step3
- 생성된 job의 start method를 통해 conditionalStep1을 실행
- on 메서드를 통해 ExitStatus.FAILED인 경우를 정의
- to 메서드를 통해 on 메서드에서 정의된 상황이 발생한 경우 보낼 곳(conditionalStep3)을 정의
- on 메서드를 통해 * 모든 경우를 정의
- end 메서드를 통해 해당 Job을 종료시킵니다
- step1 성공 시나리오: step1 -> step2 -> step3
- 생성된 job의 start method를 통해 conditionalStep1을 실행
- on 메서드를 통해 ExitStatus.FAILED인 경우를 정의가 됐지만 conditionalStep1은 성공했기 때문에 to 메서드로 정의된 곳으로 넘어가지 않습니다
- 다시 from 메서드로 정의된 Step의 ExitStatus 코드를 보고
- on 메서드로 정의된 내용(*), 모든 경우에 따라 하위 메서드들을 순차적으로 실행시킵니다
- step1 실패 시나리오: step1 -> step3
- 주요 메서드 설명
- on()
- 캐치할 ExitStatus 지정, BatchStatus가 아닌 ExitStatus 코드를 이용해 분기처리를 한다
- * 일 경우 모든 ExitStatus가 지정된다
- to()
- 다음으로 이동할 Step 정의
- from()
- 일종의 이벤트 리스너
- on(), to()의 흐름을 이용해 발생하는 ExitStatus를 추가적으로 캐치할 때 사용
- end()
- FlowBuilder()를 반환하는 end와 FlowBuilder()를 종료하는 end 두 가지로 나뉜다
- 정의된 이벤트 흐름(on -> to)을 마무리하는 end는 FlowBuilder()를 반환, 반환된 FlowBuilder()를 통해 Job 흐름을 이어나갈 수 있다
- Job.Build() 앞에 있는 end는 FlowBuilder()를 종료하는 end, Job의 흐름을 종료한다
- on()
- BatchStatus vs ExitStatus
- BatchStatus ( 4.3.3 기준 )
- Job 또는 Step 의 실행 결과를 Spring에서 기록할 때 사용하는 Enum
- org.springframework.batch.core.BatchStatus
- COMPLETED, STARTING, STARTED, STOPPING, STOPPED, FAILED, ABANDONED, UNKNOWN
- ExitStatus
- ExitStatus는 Step의 실행 후 상태
- org.springframework.batch.core.ExitStatus
- UNKNOWN, EXECUTING, COMPLETED, NOOP, FAILED, STOPPED
- BatchStatus ( 4.3.3 기준 )
3. Decide
- 위에서 조건분기를 태웠던 예제에는 2가지 문제점이 있습니다
- Step이 담당하는 역할이 2개 이상이 됩니다.
- 실제 해당 Step이 처리해야할 로직외에도 분기처리를 시키기 위해 ExitStatus 조작이 필요
- 다양한 분기 로직 처리의 어려움
- ExitStatus를 커스텀하게 고치기 위해선 Listener를 생성하고 Job Flow에 등록하는 등 번거로움이 존재
- Step이 담당하는 역할이 2개 이상이 됩니다.
- 분기만 담당하는 타입을 만들어 처리하면 Step이 담당했던 분기라는 역할을 추출할 수 있고 어려웠던 분기 로직 처리를 간단하게 만들 수 있습니다
- JobExecutionDecider interface를 구현해 분리를 담당하는 로직만 분리할 수 있습니다
- 예제 코드
// JobExecutionDecider @Slf4j @Configuration @RequiredArgsConstructor public 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"); } } } }
- "deciderJob" 이름을 갖는 Job 생성
- "startStep" step을 실행시키고 정상적으로 수행되면 decider() 를 수행
- 해당 JobExecutionDecider는 랜덤하게 추출되는 숫자를 이용하여 홀수/짝수를 기준으로 FlowExecutionStatus를 반환
- from(decider())를 통해 "ODD" ExitStatus라면 oddStep()을 수행
- from(decider())를 통해 "EVEN" ExitStatus라면 evenStep()을 수행
- 실행 결과
- 참고
- 이미 실행된 Step만 Step already complete or not restartable, so no action to execute 를 발생시키며 Skip한다
- 즉 동일한 JobParameters로 실행시켰을시 정의된 Decider는 계속 실행되며 각 Step들은 한번 실행이 된 후 다시 실행되지 않는다
728x90
반응형
'Practice > SpringBatch' 카테고리의 다른 글
Day 5. Spring Batch Chunk (0) | 2021.10.07 |
---|---|
Day 4. Spring Batch Scope (0) | 2021.09.09 |
Day 2. SpringBatch Job 메타 테이블이란? (0) | 2021.09.04 |
Day 1. Spring-Batch 환경 설정 (0) | 2021.09.01 |