ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스프링 배치 도메인 이해 -ExecutionContext
    Study/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 재시작시 이미 처리한 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

    댓글

Designed by Tistory.