-
스프링 배치 도메인 이해 -ExecutionContextStudy/Spring Batch 2024. 2. 29. 06:09
ExecutionContext
1) 기본 개념
- 프레임워크에서 유지 및 관리하는 키/값으로 된 컬렉션. (Map)
- StepExecution 또는 JobExecution 객체의 상태를 저장하는 공유 객체
- DB에 직렬화한 값으로 저장됨. {"key":"value"}
- 공유 범위
- Step 범위 - 각 Step의 StepExecution에 저장되며 Step간 서로 공유 안됨.
- Job 범위 : 각 Job의 JobExecution에 저장되며 Job간 서로 공유 안됨. 해당 Job의 Step간 서로 공유됨.
- Job -> Step1, Step2, Step3, Step4,….
Step1, Step2, Step3, Step4에서 Job 참조 가능.
스텝간 데이터 공유가 필요하다면 job의 ExecutionContext 활용
- Job -> Step1, Step2, Step3, Step4,….
- Job 재시작시 이미 처리한 Row 데이터는 건너뛰고 이후로 수행하도록 할 때 상태 정보를 활용한다.
2) 구조
BATCH_JOB_EXECUTION_CONTEXT
job_execution_id short_context serialized_context 1 { “inputCount” : “150” , “date” : “20210101”} BATCH_STEP_EXECUTION_CONTEXT
step_execution_id short_context serialized_context 1 {"batch.taskletType":"io.batch.springbatch.job.HelloJobConfiguration$1",
"name":"leaven",
"batch.stepType":"org.springframework.batch.core.step.tasklet.TaskletStep"}실습
실습 1) Job, Step간 공유 범위 확인
@RequiredArgsConstructor @Configuration public class ExecutionContextConfiguration { private final JobBuilderFactory jobBuilderFactory; private final StepBuilderFactory stepBuilderFactory; private final ExecutionContextTasklet1 executionContextTasklet1; private final ExecutionContextTasklet2 executionContextTasklet2; private final ExecutionContextTasklet3 executionContextTasklet3; @Bean public Job BatchJob() { return this.jobBuilderFactory.get("Job") .start(step1()) .next(step2()) .next(step3()) .build(); } public Step step1() { return stepBuilderFactory.get("step1") .tasklet(executionContextTasklet1) .build(); } public Step step2() { return stepBuilderFactory.get("step2") .tasklet(executionContextTasklet2) .build(); } public Step step3() { return stepBuilderFactory.get("step3") .tasklet(executionContextTasklet3) .build(); } }
Step1, Step2, Step3이 순차적으로 실행된다.
@Component public class ExecutionContextTasklet1 implements Tasklet { @Override public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { System.out.println("ExecutionContextTasklet1 has executed"); ExecutionContext jobExecutionContext = contribution.getStepExecution().getJobExecution().getExecutionContext(); ExecutionContext stepExecutionContext = contribution.getStepExecution().getExecutionContext(); String jobName = chunkContext.getStepContext().getStepExecution().getJobExecution().getJobInstance().getJobName(); String stepName = chunkContext.getStepContext().getStepExecution().getStepName(); if (jobExecutionContext.get("jobName") == null) { jobExecutionContext.put("jobName", jobName); } if (stepExecutionContext.get("stepName") == null) { stepExecutionContext.put("stepName", stepName); } System.out.println("jobName : " + jobExecutionContext.get("jobName")); System.out.println("stepName : " + stepExecutionContext.get("stepName")); return RepeatStatus.FINISHED; } }
Step1 : "jobName"이 없으면 jobExecutionContext에, "stepName"이 없으면 stepExecutionContext에 넣어준다.
- 출력
ExecutionContextTasklet1 has executed
jobName : Job
stepName : step1@Component public class ExecutionContextTasklet2 implements Tasklet { @Override public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { System.out.println("ExecutionContextTasklet2 has executed"); ExecutionContext jobExecutionContext = contribution.getStepExecution().getJobExecution().getExecutionContext(); ExecutionContext stepExecutionContext = contribution.getStepExecution().getExecutionContext(); System.out.println("jobName : " + jobExecutionContext.get("jobName")); System.out.println("stepName : " + stepExecutionContext.get("stepName")); String stepName = chunkContext.getStepContext().getStepExecution().getStepName(); if (stepExecutionContext.get("stepName") == null) { stepExecutionContext.put("stepName", stepName); } System.out.println("stepName : " + stepExecutionContext.get("stepName")); return RepeatStatus.FINISHED; } }
Step2 : jobName, stepName을 출력한다. StepExecution은 Step간 공유되지 않으므로 첫 번째 stepName 출력에서는 null이 나온다.
- 출력
ExecutionContextTasklet2 has executed
jobName : Job
stepName : null
stepName : step2@Component public class ExecutionContextTasklet3 implements Tasklet { @Override public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { System.out.println("ExecutionContextTasklet3 has executed"); Object name = chunkContext.getStepContext().getStepExecution().getJobExecution().getExecutionContext().get("name"); if (name == null) { chunkContext.getStepContext().getStepExecution().getJobExecution().getExecutionContext().put("name", "user1"); // 아래에서 실패후 재시작했을 때 name 확인. System.out.println("ExecutionContextTasklet3 fail!"); throw new RuntimeException("step has failed"); } return RepeatStatus.FINISHED; } }
Step3 : 의도적으로 예외를 날린다.
- 출력
ExecutionContextTasklet3 has executed
ExecutionContextTasklet3 fail!실습 2) 실습 1 재시작
Step1, Step2는 실습 1에서 성공하였기 때문에 Skip하고 Step3을 실행하게 된다.
실습 2에서는 실습 1에서 저장된 "name"이 있어 null이 아니기 때문에 에러가 발생하지 않는다.
- 출력
ExecutionContextTasklet3 has executed'Study > Spring Batch' 카테고리의 다른 글
스프링 배치 도메인 이해 -JobLauncher (0) 2024.03.08 스프링 배치 도메인 이해 -JobRepository (0) 2024.02.29 스프링 배치 도메인 이해 -StepContribution (0) 2024.02.15 스프링 배치 도메인 이해 -StepExecution (0) 2024.02.15 스프링 배치 도메인 이해 -Step (0) 2024.02.15