Coding/Spring Batch

[SpringBatch 실습] 6. ExecutionContext (StepExecution, JobExecution 관계) 예제로 흐름 파악하기

shbada 2022. 5. 24. 16:51
728x90
반응형

ExecutionContext 개념

https://devfunny.tistory.com/485

 

[스프링 배치] 배치 잡의 세션 ExecutionContext

상황 스프링 배치가 진행중이다. 진행 도중에 오류가 발생했고, 실패한 시점부터 처리를 다시 시작해야한다. 이 경우에 스프링 배치는 실패한 시점을 어떻게 알아낼 수 있을까? 실패시마다, 사

devfunny.tistory.com

 

 

 

실습해보기

  • ExecutionContextConfiguration.java
  • ExecutionContextTasklet1.java
  • ExecutionContextTasklet2.java
  • ExecutionContextTasklet3.java
  • ExecutionContextTasklet4java

 

ExecutionContextConfiguration.java

import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/*
--job.name=executionContextTestJob
 */
@Configuration
@RequiredArgsConstructor
public class ExecutionContextConfiguration {
    // job 생성
    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    /* tasklet */
    private final ExecutionContextTasklet1 executionContextTasklet1;
    private final ExecutionContextTasklet2 executionContextTasklet2;
    private final ExecutionContextTasklet3 executionContextTasklet3;
    private final ExecutionContextTasklet4 executionContextTasklet4;

    @Bean
    public Job executionContextTestJob() {
        return this.jobBuilderFactory.get("executionContextTestJob")
                .start(executionContextTestStep1())
                .next(executionContextTestStep2())
                .next(executionContextTestStep3())
                .next(executionContextTestStep4())
                .build();
    }

    @Bean
    public Step executionContextTestStep1() {
        return stepBuilderFactory.get("executionContextTestStep1")
                .tasklet(executionContextTasklet1)
                .build();
    }

    @Bean
    public Step executionContextTestStep2() {
        return stepBuilderFactory.get("executionContextTestStep2")
                .tasklet(executionContextTasklet2)
                .build();
    }

    @Bean
    public Step executionContextTestStep3() {
        return stepBuilderFactory.get("executionContextTestStep3")
                .tasklet(executionContextTasklet3)
                .build();
    }

    @Bean
    public Step executionContextTestStep4() {
        return stepBuilderFactory.get("executionContextTestStep4")
                .tasklet(executionContextTasklet4)
                .build();
    }
}

 

ExecutionContextTasklet1.java

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
public class ExecutionContextTasklet1 implements Tasklet {
    /**
     * 비즈니스 로직 구현
     * @param stepContribution
     * @param chunkContext
     * @return
     * @throws Exception
     */
    @Override
    public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
        System.out.println("step1 was executed");

        /* ExecutionContext */
        ExecutionContext jobExecutionContext = stepContribution.getStepExecution()
                .getJobExecution().getExecutionContext();

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

        /* jobName */
        String jobName = chunkContext.getStepContext().getStepExecution()
                .getJobExecution().getJobInstance().getJobName();

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

        // jobExecutionContext 안에 jobName 을 key 로 하는 데이터를 저장한 경우가 없을때
        // job 최초 실행시 put 이 될것이다.
        if (jobExecutionContext.get("jobName") == null) {
            jobExecutionContext.put("jobName", jobName);
        }

        // job 최초 실행시 put 이 될것이다.
        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;
    }
}

 

1) jobExecutionContext

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

 

2) stepExecutionContext 

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

 

3) jobName 가져오기

/* jobName */
String jobName = chunkContext
                    .getStepContext()
                    .getStepExecution()
                    .getJobExecution()
                    .getJobInstance()
                    .getJobName();

 

4) stepName 가져오기

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

 

5) jobExecutionContext에 데이터 저장

jobExecutionContext에 "jobName"을 key로 하는 데이터를 저장한 경우가 없을때 데이터를 put() 하자.

if (jobExecutionContext.get("jobName") == null) {
    jobExecutionContext.put("jobName", jobName);
}

 

6) stepExecutionContext에 데이터 저장

stepExecutionContext에 "stepName"을 key로 하는 데이터를 저장한 경우가 없을때 데이터를 put() 하자.

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

 

7) 5)~6) 결과 로그 출력

System.out.println("jobName = " +  jobExecutionContext.get("jobName"));
System.out.println("stepName = " +  stepExecutionContext.get("stepName"));

 

 

ExecutionContextTasklet2.java

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
public class ExecutionContextTasklet2 implements Tasklet {
    /**
     * 비즈니스 로직 구현
     * @param stepContribution
     * @param chunkContext
     * @return
     * @throws Exception
     */
    @Override
    public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
        System.out.println("step2 was executed");

        /* ExecutionContext */
        ExecutionContext jobExecutionContext = stepContribution.getStepExecution()
                .getJobExecution().getExecutionContext();

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

        /* Tasklet1 에서 설정한 ExecutionContext 확인 */
        System.out.println("jobName : " + jobExecutionContext.get("jobName"));
        System.out.println("stepName : " + stepExecutionContext.get("stepName"));

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

        // step 끼리 공유가 안되므로 null 일 것이다.
        if (stepExecutionContext.get("stepName") == null) {
            stepExecutionContext.put("stepName", stepName);
        }

        return RepeatStatus.FINISHED;
    }
}

 

1) ExecutionContextTasklet1 에서 저장한 데이터를 꺼내보자

  • JobExecutionContext은 Step 끼리 공유할 수 있다.
  • StepExecutionContext은 Step 끼리 공유할 수 없다. Job 끼리만 공유가 가능하다.
System.out.println("jobName : " + jobExecutionContext.get("jobName"));
System.out.println("stepName : " + stepExecutionContext.get("stepName")); // null

 

결과
jobName = executionContextTestJob
stepName = executionContextTestStep1

 

2) 다시 "stepName"을 key로 데이터를 저장하자.

위 1)번에서 step 끼리는 공유가 안되므로 stepExecutionContext.get("stepName")은 null이다.

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

// step 끼리 공유가 안되므로 null 일 것이다.
if (stepExecutionContext.get("stepName") == null) {
    stepExecutionContext.put("stepName", stepName);
}

 

결과
jobName : executionContextTestJob
stepName : null

 

 

ExecutionContextTasklet3.java

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
public class ExecutionContextTasklet3 implements Tasklet {
    /**
     * 비즈니스 로직 구현
     * @param stepContribution
     * @param chunkContext
     * @return
     * @throws Exception
     */
    @Override
    public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
        System.out.println("step3 was executed");

        // 최초일 경우, 저장한적 없으므로 null
        Object name = chunkContext.getStepContext().getStepExecution()
                .getJobExecution().getExecutionContext().get("name");

        if (name == null) {
            chunkContext.getStepContext().getStepExecution()
                    .getJobExecution().getExecutionContext().put("name", "user1");
            throw new RuntimeException("step3 was failed"); // 예외 발생시키기
        }

        return RepeatStatus.FINISHED;
    }
}

 

1) 최초 실행의 경우 name 은 null이다.

Object name = chunkContext.getStepContext()
                        .getStepExecution()
                        .getJobExecution()
                        .getExecutionContext()
                        .get("name");

 

2) 최초 실행의 경우, 에러를 고의로 발생시킨다.

여기서 "name"을 key로 "user1"을 value로 데이터를 저장한 후에 예외를 발생시켰다.

if (name == null) {
    chunkContext.getStepContext().getStepExecution()
            .getJobExecution().getExecutionContext().put("name", "user1");
    throw new RuntimeException("step3 was failed"); // 예외 발생시키기
}

이때 에러를 발생시킨 이유는, 다음에 실행될 ExecutionContextTasklet4 에서 재실행했을때 name 을 가져올 수 있는지를 테스트해보기 위해서다.

 

오류 발생
java.lang.RuntimeException: step3 was failed

 

 

 

Batch 재실행

  • 최초 실행시
ExecutionContextTasklet1 성공
ExecutionContextTasklet2 성공
ExecutionContextTasklet3 실패
ExecutionContextTasklet4 ExecutionContextTasklet3에서 에러 발생했으므로 미실행
(배치 재수행시, ExecutionContextTasklet3 부터 수행)

 

ExecutionContextTasklet3.java

1) name 에 값이 들어있으므로 PASS

if (name == null) {
    chunkContext.getStepContext().getStepExecution()
            .getJobExecution().getExecutionContext().put("name", "user1");
    throw new RuntimeException("step3 was failed"); // 예외 발생시키기
}

 

결과
step3 was executed

 

 

ExecutionContextTasklet4.java

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
public class ExecutionContextTasklet4 implements Tasklet {
    /**
     * 비즈니스 로직 구현
     * @param stepContribution
     * @param chunkContext
     * @return
     * @throws Exception
     */
    @Override
    public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
        System.out.println("step4 was executed");

        // 재실행했을때 tasklet3 에서 실패했지만 name 은 executionContext 에 저장되어있는데, 정상적으로 출력될까?
        // tasklet3 에서 name 을 저장 후에 오류가 발생했으므로 정상적으로 찍힌다.
        Object name = chunkContext.getStepContext().getStepExecution()
                .getJobExecution().getExecutionContext().get("name");

        System.out.println("name : " + name);

        return RepeatStatus.FINISHED;
    }
}

 

1) 실행 로그 확인

ExecutionContextTasklet3에서 에러 발생 전에 저장한 "name" 데이터가 정상 출력된다.

step4 was executed
name : user1

 

 

최종 결과 DB 테이블 조회

1) BATCH_JOB_EXECUTION

JOB_EXECUTION_ID STATUS EXIT_CODE
27 FAILED FAILED
28 (재수행) COMPLETED COMPLETED

 

2) BATCH_JOB_INSTANCE

COLUMN VALUE
JOB_NAME executionContextTestJob

 

3) BATCH_STEP_EXECUTION

STEP_EXECUTION_ID STEP_NAME JOB_EXECUTION_ID STATUS EXIT_CODE
33 executionContextTestStep1 27 COMPLETED COMPLETED
34 executionContextTestStep2 27 COMPLETED COMPLETED
35 executionContextTestStep3 27 FAILED FAILED
36 (재수행) executionContextTestStep3 28 COMPLETED COMPLETED
37 (재수행) executionContextTestStep4 28 COMPLETED COMPLETED

 

 

 

반응형