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

반응형
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

 

 

 

반응형

Designed by JB FACTORY