Spring Batch

Spring Batch 도메인 ExecutionContext

NEWDODORIPYO 2022. 8. 30. 17:35

기본 개념

  • 프레임워크에서 유지 및 관리하는 “키 / 값”으로 된 컬렉션으로 StepExecution 또는 JobExecution 객체의 상태(state)를 저장하는 공유 객체
  • DB 에 직렬화 한 값으로 저장됨 {”key” : “value”}
  • 공유 범위
    • Step 범위 = 각 Step의 StepExecution에 저장되며 Step 간 서로 공유 안됨
    • Job 범위 = 각 Job의 JobExecution에 저장되며 Job 간 서로 공유 안되며 해당 Job의 Step 간 서로 공유됨
  • Job 재 시작 시 이미 처리한 Row 데이터는 건너뛰고 이후로 수행하도록 할 때 상태 정보를 활용한다.

구조

ExecutionContext

Map<String , Object> map = new ConcurrentHashMap
  • 유지 , 관리에 필요한 키값 설정

그림으로 이해하기

위 그림이 저장될 때👇

BATCH_JOB_EXECUTION_CONTEXT

job_execution_id short_context serialized_context
1 { "inputCount" : "150" , "date" : "20220819"}  

BATCH_STEP_EXECUTION_CONTEXT

step_execution_id                           short_context    serialized_context
1 {”batch.taskletType”:”io.batch.springbatch.job.HellojobConfiguation$1”,

”name”:”leaven”,

”batch.stepType”:”org.springframework.batch.core.step.tasklet.TaskletStep”}
 

코드로 보기

JobConfiguration

package io.batch;

import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Map;

@Configuration
@RequiredArgsConstructor
public class DBJobConfiguration {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;
    private final ExecutionContextTasklet1 executionContextTasklet1;
    private final ExecutionContextTasklet2 executionContextTasklet2;
    private final ExecutionContextTasklet3 executionContextTasklet3;
    private final ExecutionContextTasklet4 executionContextTasklet4;

    @Bean
    public Job job(){
        return jobBuilderFactory.get("job")
                .start(step1())
                .next(step2())
                .next(step3())
                .next(step4())
                .build();//build를 해주면 실제 Job 생성
    }

    @Bean
    public Step step1() {
        return stepBuilderFactory.get("step1")
                .tasklet(executionContextTasklet1)
                .build();
    }
    @Bean
    public Step step2() {
        return stepBuilderFactory.get("step2")
                .tasklet(executionContextTasklet2)
                .build();
    }
    @Bean
    public Step step3() {
        return stepBuilderFactory.get("step3")
                .tasklet(executionContextTasklet3)
                .build();
    }
    @Bean
    public Step step4() {
        return stepBuilderFactory.get("step4")
                .tasklet(executionContextTasklet4)
                .build();
    }

}
  • 4개의 Step 가 돌아간다
  • 3번째 Tasklet에서 실패하게 코드를 작성
  • 결과를 확인해보자

ExecutionContextTasklet1

package io.batch;

import lombok.extern.log4j.Log4j2;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.stereotype.Component;

@Log4j2
@Component
public class ExecutionContextTasklet1 implements Tasklet {

    //StepContribution 클래스 안에있는 StepExecution 을 참조해서 ExecutionContext 을 활용하는 방법을 사용하고자한다
    @Override
    public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
        log.info("step1 돌아간다아~");

        ExecutionContext jobExecutionContext = stepContribution.getStepExecution().getJobExecution().getExecutionContext();
        ExecutionContext stepExecutionContext = stepContribution.getStepExecution().getExecutionContext();

        //getJobName 꺼내기
        String jobName =  chunkContext.getStepContext().getStepExecution().getJobExecution().getJobInstance().getJobName();

        //getStepName 꺼내기
        String stepName = chunkContext.getStepContext().getStepExecution().getStepName();

        //jobName 가 null 이라는건 jobExecutionContext 안에 jobName 키로하는 값을 저장한 적이 없는 경우
        if(jobExecutionContext.get("jobName") == null){
            jobExecutionContext.put("jobName",jobName);
        }

        if(stepExecutionContext.get("stepName") == null){
            stepExecutionContext.put("stepName",stepName);
        }

        log.info("jobName : " + jobExecutionContext.get("jobName"));
        log.info("stepName :" + jobExecutionContext.get("stepName"));

        return RepeatStatus.FINISHED;
    }
}

ExecutionContextTasklet2

package io.batch;

import lombok.extern.log4j.Log4j2;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.stereotype.Component;

@Component
@Log4j2
public class ExecutionContextTasklet2 implements Tasklet {

    //StepContribution 클래스 안에있는 StepExecution 을 참조해서 ExecutionContext 을 활용하는 방법을 사용하고자한다
    @Override
    public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
        log.info("step1 이 성공했구나? ");
        log.info("step2 돌아간다아~");

        ExecutionContext jobExecutionContext = chunkContext.getStepContext().getStepExecution().getJobExecution().getExecutionContext();
        ExecutionContext stepExecutionContext = chunkContext.getStepContext().getStepExecution().getExecutionContext();

        log.info("jobName : " + jobExecutionContext.get("jobName")); //step1 이 성공했다면 jobExecutionContext 에서는 step1 에서 입력된 값이 보일것
        log.info("stepName : " + stepExecutionContext.get("stepName")); //step1 이 성공했어도 step 끼리는 공유가 안되기때문에 값이 없다

        String stepName = chunkContext.getStepContext().getStepExecution().getStepName();

        if(stepExecutionContext.get("stepName") == null){
            stepExecutionContext.put("stepName",stepName);
        }

        return RepeatStatus.FINISHED;
    }
}

ExecutionContextTasklet3

package io.batch;

import lombok.extern.log4j.Log4j2;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.stereotype.Component;

@Component
@Log4j2
public class ExecutionContextTasklet3 implements Tasklet {

    //StepContribution 클래스 안에있는 StepExecution 을 참조해서 ExecutionContext 을 활용하는 방법을 사용하고자한다
    @Override
    public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
        log.info("step3 실패 예외발생~");
        Object name =  chunkContext.getStepContext().getStepExecution().getJobExecution().getExecutionContext().get("name");

        if(name == null){
            chunkContext.getStepContext().getStepExecution().getJobExecution().getExecutionContext().put("name","user1");
            throw new RuntimeException("step2 was failed");
        }
        return RepeatStatus.FINISHED;
    }
}

ExecutionContextTasklet4

package io.batch;

import lombok.extern.log4j.Log4j2;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.stereotype.Component;

@Component
@Log4j2
public class ExecutionContextTasklet4 implements Tasklet {

    //StepContribution 클래스 안에있는 StepExecution 을 참조해서 ExecutionContext 을 활용하는 방법을 사용하고자한다
    @Override
    public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {

        log.info("step3 에서 저장한 name ");
        log.info("name : " + chunkContext.getStepContext().getStepExecution().getJobExecution().getExecutionContext().get("name"));
        log.info("step4 돌아간다아~");
        return RepeatStatus.FINISHED;
    }
}

실행 ~~

batch_step_execution 테이블

  • 테이블은 보면 총 2번 실행했고 첫 번째 실행 때는 정상적으로 진행된 step 1 , 2 그다음 3번에서 실패해서 거기서 종료되고 한 번 더 실행헀을때는 성공했던 step1 , 2를 제외하고 3 , 4 가 실행된 걸 볼 수 있다

'Spring Batch' 카테고리의 다른 글

Spring Batch 도메인 StepContribution  (0) 2022.08.30
Spring Batch 도메인 StepExecution  (0) 2022.08.24
Spring Batch 도메인 Step  (0) 2022.08.23
Spring Batch 도메인 JobExecution  (0) 2022.08.22
Spring Batch 도메인 JobParameters  (0) 2022.08.19