3.Spring Batch 도메인 이해하기
Spring Batch

3.Spring Batch 도메인 이해하기

반응형

Job

1. 기본개념

  • 배치 계층 구조에서 가장 상위에 있는 개념으로서 하나의 배치작업 자체를 의미함
    • “API 서버의 접속 로그 데이터를 통계 서버로 옮기는 배치“ 인 Job 자체를 의미한다.
  • Job Configuration 을 통해 생성되는 객체 단위로서 배치작업을 어떻게 구성하고 실행할 것인지 전체적으로 설정하고 명세해 놓은 객체
  • 배치 Job 을 구성하기 위한 최상위 인터페이스이며 스프링 배치가 기본 구현체를 제공한다
  • 여러 Step 을 포함하고 있는 컨테이너로서 반드시 한개 이상의 Step으로 구성해야 함

2. 기본 구현체

  • SimpleJob
    • 순차적으로 Step 을 실행시키는 Job
    • 모든 Job에서 유용하게 사용할 수 있는 표준 기능을 갖고 있음
  • FlowJob
    • 특정한 조건과 흐름에 따라 Step 을 구성하여 실행시키는 Job
    • Flow 객체를 실행시켜서 작업을 진행함

JobInstance

1. 기본개념

  • Job이 실행 될 때 생성되는 Job의 논리적 실행 단위 객체로서 고유하게 식별 가능한 작업 실행을 나타냄
  • Job의 설정과 구성은 동일하지만 Job이 실행되는 시점에 처리하는 내용은 다르기 때문에 Job의 실행을 구분해야함
    • 예를 들어 하루에 한 번 씩 배치 Job이 실행된다면 매일 실행되는 각각의 Job 을 JobInstance 로 표현합니다.
  • JobInstance 생성 및 실행
    • 처음 시작하는 Job + JobParameter 일 경우 새로운 JobInstance 생성
    • 이전과 동일한 Job + JobParameter 으로 실행 할 경우 이미 존재하는 JobInstance 리턴
      • 내부적으로 JobName + jobKey (jobParametes 의 해시값) 를 가지고 JobInstance 객체를 얻음
  • Job 과는 1:M 관계

2. BATCH_JOB_INSTANCE 테이블과 매핑

  • JOB_NAME (Job) 과 JOB_KEY (JobParameter 해시값) 가 동일한 데이터는 중복해서 저장할 수 없음

JobParameter

1. 기본개념

  • Job을 실행할 때 함께 포함되어 사용되는 파라미터를 가진 도메인 객체
  • 하나의 Job에 존재할 수 있는 여러개의 JobInstance를 구분하기 위한 용도
  • JobParameters와 JobInstance는 1:1 관계

2. 생성 및 바인딩

  • 어플리케이션 실행 시 주입
    • Java -jar LogBatch.jar requestDate=20210101
  • 코드로 생성
    • JobParameterBuilder, DefaultJobParametersConverter
  • SpEL 이용
    • @Value(“#{jobParameter[requestDate]}”), @JobScope, @StepScope 선언 필수

3. BATCH_JOB_EXECUTION_PARAM 테이블과 매핑

  • JOB_EXECUTION 과 1:M 의 관계

예제코드

변수에 값을 넣어서 파라미터 넣기

java -jar spring-batch-0.0.1-SNAPSHOT.jar 'name=user1' 'seq(long)=2L' 'date(date)=2021/01/01' 'age(double)=16.5'

IDE를 통해서 JobParameter 변수값 설정하기

JobExeccution

1. 기본개념

  • JobInstance 에 대한 한 번의 시도를 의미하는 객체로서 Job 실행 중에 발생한 정보들을 저장하고 있는 객체
    • 시작시간, 종료시간 ,상태(시작됨,완료,실패),종료상태의 속성을 가짐
  • 그러나 JobExecution은 FAILD 상태라면 여러번 실행할 수 있다.
    • JobExecution은 'FAILED' 또는 'COMPLETED‘ 등의 Job의 실행 결과 상태를 가지고 있음
    • JobExecution 의 실행 상태 결과가 'COMPLETED’ 면 JobInstance 실행이 완료된 것으로 간주해서 재실행이 불가함
      • 이미 성공한 결과라면 더 이상 실행할 필요가 없다는 의미
    • JobExecution의 실행 상태 결과가 'FAILED’ 면 JobInstance 실행이 완료되지 않은 것으로 간주해서 재실행이 가능함
      • FAILDED 상태라면 JobParameter 가 동일한 값으로 Job 을 실행할지라도 JobInstance 를 계속 실행할 수 있음
      • 그렇다고 JobInstance가 새롭게 생성되는것은 아니다. 실행만 다시 할 수 있을뿐이다.
    • JobExecution 의 실행 상태 결과가 'COMPLETED’ 될 때까지 하나의 JobInstance 내에서 여러 번의 시도가 생길 수 있음JobInstance 과의 관계

💡 즉 JobInstnace와 JobExecution의 차이점은 JobInstance는 단 한 번만 생성되는 반면에 JobExecution은 COMPLETED 상태가 될때가지 반복 실행될 수 있다.

2. BATCH_JOB_EXECUTION 테이블과 매핑

  • 위에서 말한 JobInstance는 한 번만 생성되고 JobExectuion은 여러번 실행된다는 개념은 테이블에서 확인할 수 있다.
  • JobInstance 와 JobExecution 는 1:M 의 관계로서 JobInstance 에 대한 성공/실패의 내역을 가지고 있음

3.JobExecution의 Class의 구성도

  • JobParameters : 객체 저장
  • JobInstance : 객체 저장
  • JobInstance : 실행하는 동안 유지해야 하는 데이터를 담고 있음
  • BatchStatus : 실행 상태를 나타내는 Eum 클래스 (COMPLETED, STARTING, STARTED, STOPPING, STOPPED, FAILED, ABANDONED, UNKNOWN)
  • ExitStatus : 실행 결과를 나타내는 클래스로서 종료코드를 포함(UNKNOWN, EXECUTING, COMPLETED, NOOP, FAILED, STOPPED)
  • failuterExceptions : Job실행중발생한예외리스트
  • startTime : Job을 실행할 때의 시스템 시간
  • createTiem : JobExecution이 처음 저장될 때의 시스템 시간
  • endTime : 성공 여부와 상관없이 실행이 종료되는 시간
  • lastUpdated : JobExecution이 마지막 저장될 때의 시스템 시간

💡 StepExecution 클래스 JobExecution 객체를 확인하면 해당 정보를 확인할 수 있다.

참고.BATCH_JOB_EXECUTION 테이블

4.Spring Batch의 JobExecution 관점의 흐름

  • Spring Batch의 흐름은 JobLancher가 Job과 JobParamters에 의존하여 실행된다.
  • 이후 run 메소드를 통해 JobRepository를 조회하게 된다.
    • 이때 DB를 조회해서 Job과 JobParameters가 존재할 경우에는 기존 JobInstance를 리턴한다.
      • 이후 Job이 성공 완료인 상태인 경우 JobInstnceAlreadyCompleteException 예외를 발생시키면서 실행이 불가능해진다.
      • Job 시도 결과가 실패인 경우에는 새로운 실행 객체를 생성하면서 (new JobExecution) JobInstance를 재실행한다.
    • 만약 DB를 조회해서 데이터가 존재하지 않는 경우 새로운 Instance(new JobInstance)를 생성하고 새로운 Execution(new JobExecution)을 실행한다.

5.예제 테스트

  • 아래와 같은 예제 코드를 실행해보자
package com.example.springbatch_3_1_3_instance;

import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.*;
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.Date;

@RequiredArgsConstructor
@Configuration
public class JobExecutionConfiguration {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job BatchJob() {
        return this.jobBuilderFactory.get("Job")
                .start(step1())
                .next(step2())
                .build();
    }

    @Bean
    public Step step1() {
        return stepBuilderFactory.get("step1")
                .tasklet(new Tasklet() {
                    @Override
                    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
                        System.out.println("step1 has executed");
                        return RepeatStatus.FINISHED;
                    }
                })
                .build();
    }
    @Bean
    public Step step2() {
        return stepBuilderFactory.get("step2")
                .tasklet((contribution, chunkContext) -> {
                    System.out.println("step2 has executed");
                    throw new RuntimeException("step2 has failed");//1
//                    return RepeatStatus.FINISHED;//2
                })
                .build();
    }
}

실행순서

1.step2의 2번 부분의 주석을 걸고 실행한다.

  • Step2에서 예외를 throw 해줬기 때문에 JobExectution이 실행되고 상태가 Fail로 끝나는것을 알 수 있다.

  • 이후 JobInsatnce를 확인해보면 객체가 한 개 생성된것을 확인할 수 있다.

  • 한 번 더 실행해서 실패 된 execution을 생성해보자

  • Instance는 여전히 한 개만 생성된 것을 확인 할 수 있다.

  • 이제는 2번 부분의 주석을 해제하고 1번 부분에 주석을 걸어본 뒤 실행해보자.

  • 성공적으로 수행되었다는것을 확인하였다. 이후 한 번 더 실행해보자
  • 아래와 같이 A job instance already exists and is complete for parameters 라는 오류 메시지를 통해 수행이 안되는것을 확인할 수 있다.
  • JobExcution이 성공할 때까지만 반복 수행되는것을 알 수 있다.

  • 그림으로 확인해보면 아래와 같다.

  • 이로써 개념을 코드와 함께 증명하였다.

Step

1. 기본개념

  • Batch job을 구성하는 독립적인 하나의 *단계로서 실제 배치 처리를 정의하고* 컨트롤하는 데 필요한 모든 정보를 가지고 있는 도메인 객체
  • 단순한 단일태스크만이 아니라 입력과 처리 그리고 출력과 관련된 복잡한 비즈니스 로직을 포함하는 모든 설정들을 담고있다.
  • 배치작업을 어떻게 구성하고 실행할 것인지 Job 의 세부 작업을 Task 기반으로 설정하고 명세해 놓은 객체
  • 모든 Job은 하나 이상의 step으로 구성됨

2. Step의 기본 구현체

TaskletStep

  • 가장 기본이 되는 클래스로서 Tasklet 타입의 구현체들을 제어한다
  • Step의 자신마다의 Tasklet을 가지고 있다.

PartitionStep

  • 멀티 스레드 방식으로 Step 을 여러 개로 분리해서 실행한다

JobStep

  • Step 내에서 Job 을 실행하도록 해야하므로 Job의 정보를 가지는 객체가 필요하다
  • JobStep의 다음 실행할 Job의 정보를 가지고 있다.
  • start().next().next()를 생각해
    • 각 Step안에서 역할을 수행한 뒤에도 다음 Job을 호출해야한다.
    • 그러므로 Step안에는 다음 Job의 정보를 가지고 있어야만 수행이 가능하다.
    • 이는 필터체인 구조와 유사하다.

FlowStep

  • Step 내에서 Flow 를 실행하도록 한다

3.Step의 Interface 구조

  • Step안에는 Step을 실행시키는 execute 메소드가 존재한다.
  • 실행 결과 상태는 StepExecution에 저장된다.
  • Step의 구현을 위해 AbstractStep이 존재한다.
    • name : step 이름
    • startLimit : Step 실행 제한 횟수
    • allowStartComplete : Step 실행이 완료된 후 실행 여부
    • stepExecutionListener : Step 이벤트 리스터
    • jobRepository : Step 메타데이터 저장소
  • AbstractStep의 정보
  • 그 밑에 각 구현체인 JobStep, TaskletStep, FlowStep, PartitionStep으로 구성되어있다.

4.Job → Step →Tasklet의 흐름

  • 등록된 Bean을 통해 등록한 Job에서에서 각각의 Step을 execute한다.
  • Step안에서는 Step마다의 Tasklet이 동작하면서 로직을 수행한다.
  • Tasklet에는 ItemReader, ItemProcessor, ImteWrite가 동작하면서 로직을 수행한다.

  • Tasklet은 맨 위의 첫 번째와 두 번째 그림처럼 2가지 방식으로 사용할 수 있다.
    • 첫 번째 방식은 Tasklet을 사용자가 직접 정의하는 방식이다.
    • 2번째 방식은 ChunkOrientedTasklet이라고 한다.
    • 자신의 로직은 reader와 writer에 정의해서 동작시키는 방식이다. 실제로는 spring이 Tasklet을 돌려주기 때문에 1번과 같다.
    • 3번째 그림은 JobStep의 실행 예제이다. 각 launcher와 prameterExtratocor를 통해 Step을 실행시킨다는것을 알 수 있다.
    • 4번째 그림은 Step에서 flow를 실행하는 예제이다.

StepExecution

1. 기본개념

  • Step 에 대한 한번의 시도를 의미하는 객체로서 Step 실행 중에 발생한 정보들을 저장하고 있는 객체
    • 시작시간, 종료시간 ,상태(시작됨,완료,실패), commit count, rollback count 등의 속성을 가짐
  • Step 이 매번 시도될 때마다 생성되며 각 Step 별로 생성된다
    • Job안의 Step마다 하나의 Execution이 생성되는것이다.
  • Job이 재시작 하더라도 이미 성공적으로 완료된 Step은 재실행 되지 않고 실패한Step만 실행된다.
@Bean
    public Job BatchJob() {
        return this.jobBuilderFactory.get("Job")
                .start(step1())
                .next(step2())
								.next(step3())
                .build();
    }
  • 예를 들어 위와 같은 코드가 있는 경우 이전 실행에너 step1이 수행에 성공하고 step2가 실패했다면 다음 수행에서는 step2부터 실행되는것을 의미한다.
    • 다시 시작할 수 는 있는데 옵션을 별도로 설정해야한다.
    • 후작업이었던 step3 또한 수행되지 않는다.
  • 이전 단계 Step이 실패해서 현재 Step을 실행하지 않았다면 StepExecution을 생성하지 않는다. Step이 실제로 시작됐을 때만 StepExecution을 생성한다
    • 예를 들어 위와 같은 코드 예시에서 step1만 성공하고 step2가 실패할 경우 step2는 실행중에 실패한것이기 때문에 StepExecution이 생성된다.
    • 그러나 Step3는 실행을 하지도 않았기 때문에 StepExecution이 생성되지 않는다.
  • JobExecution 과의 관계
    • Step의 StepExecution 이 모두 정상적으로 완료 되어야 JobExecution이 정상적으로 완료된다.
    • Step의 StepExecution 중 하나라도 실패하면 JobExecution 은 실패한다
  1. BATCH_STEP_EXECUTION 테이블과 매핑
  • JobExecution 와 StepExecution 는 1:M 의 관계
  • 하나의 Job 에 여러 개의 Step 으로 구성했을 경우 각 StepExecution 은 하나의 JobExecution 을 부모로 가진다

그림 예제

  • 1행의 JobParameter:2021.01.01의 그림 흐름을 보자
  • 일별정산 Job에서 JobExecution을 실행하여 Step1과 Step2가 각각 실행되어 정상적으로 완료되었다.
  • 그러나 2행의 JobParameter:2021.01.012의 그림을 보면 Step1은 성공했으나 Step2는 실패한것을 볼 수 있다.
  • 이로 인해 BATCH_STEP_EXECUTION의 상태 4가지중 한 개만 status가 FAILED인것을 알 수 있다.
  • 또한 BATCH_JOB_EXECUTION의 JobExecution_ID가 2인 컬럼만 Stp2가 수행 중 실패했기 때문에 status가 FAILD인것을 알 수 있다.

💡 JobInstance와 JobExecution은 1:M 관계이고 JobExecution과 StepExecution은 1 : M 관계이다.

2.StepExecution 객체 분석

  • JobExecution: JobExecution 객체 저장
  • stepName : Step 이름
  • BatchStatus : 실행 상태를 나타내는 Eum 클래스 (COMPLETED, STARTING, STARTED, STOPPING, STOPPED, FAILED, ABANDONED, UNKNOWN)
  • readCount: 성공적으로 read한 아이템 수
  • writeCount: 성공적으로 write한 아이템 수
  • commitCount: 실행중에커밋된트랜잭션수
  • rollbackCount: 트랜잭션 중 롤백된 횟수
  • readSkipCount: read에 실패해서 스킵된 횟수
  • processSkipCount: process에 실패해서 스킵된 횟수
  • writeSkipCount: write에 실패해서 스킵된 횟수
  • filterCount: ItemProcessor 에 의해 필터링된 아이템 수
  • startTime: Job을 실행할 때의 시스템 시간
  • endTime: 성공 여부와 상관없이 실행이 종료되는 시간
  • lastUpdated: JobExecution이 마지막 저장될 때의 시스템 시간
  • ExecutionContext: 실행하는 동안 유지해야 하는 데이터를 담고 있음
  • ExitStatus: 실행결과를 나타내는 클래스로서 종료코드를 포함(UNKNOWN, EXECUTING, COMPLETED, NOOP, FAILED, STOPPED)
  • failureExceptions: Job 실행 중 발생한 예외 리스트

코드 예제

package com.example.springbatch_3_1_3_instance;

import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.*;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@RequiredArgsConstructor
@Configuration
public class StepExecutionConfiguration {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job BatchJob() {
        return this.jobBuilderFactory.get("Job")
                .start(step1())
                .next(step2())
                .next(step3())
                .build();
    }

    @Bean
    public Step step1() {
        return stepBuilderFactory.get("step1")
                .tasklet((stepContribution, chunkContext) -> {
                    System.out.println(">> step1 has executed");
                    return RepeatStatus.FINISHED;
                })
                .build();
    }

    @Bean
    public Step step2() {
        return stepBuilderFactory.get("step2")
                .tasklet((stepContribution, chunkContext) -> {
                    System.out.println(">> step2 has executed");
                    throw new RuntimeException(); //1
//                    return RepeatStatus.FINISHED; //2
                })
                .build();
    }

    @Bean
    public Step step3() {
        return stepBuilderFactory.get("step3")
                .tasklet((stepContribution, chunkContext) -> {
                    System.out.println(">> step3 has executed");
                    return RepeatStatus.FINISHED;
                })
                .build();
    }
}
  • 예제 코드를 수행하여 위 그림과 같은 흐름을 만들어보자.
  • 먼저 예제 코드를 그대로 수행할 경우 Step1이 실행이된다.
    • JobExecution이 생성된다
    • Step1의 StepExecution이 생성된다. (STATUS: 성공)
    • Step2의 StpeExecution이 생성된다 (STATUS: 실패)

  • Step3은 Step2에서 실패하므로 실행이 안된다.

실행결과(Step3의 실행 출력문을 찾아볼 수 없다.)

  • 이번에는 예제코드의 2로 적힌 줄의 주석을 해제하고 실행해보자
  • Step1까지는 실행에 성공하였으므로 StepExecution은 Step2부터 다시 시작한다.
    • Step2의 StepExecution 추가된다
    • Step3의 StepExecution 추가된다.
  • Step1 영역은 이전 수행에서 성공했으므로 아에 수행되지 않는다.

  • 새로운 JobExecution이 새로 추가된다.
  • 이번에는 예제코드의 1로 적힌 줄을 주석처리하고 2번 영역의 주석을 해제하고 수행해보자

실행결과(Step1의 실행 출력문을 찾아볼 수 없다.)

JobExecution의 정상적인 성공

StepExecution의 정상적인 성공으로 인한 2개의 Column 데이터 추가

StepContribution

1. 기본개념

  • 청크 프로세스의 변경 사항을 버퍼링 한 후 StepExecution 상태를 업데이트하는 도메인 객체
    • 큰 범위에서는 청크 프로세스는 Tasklet을 포함한다.
    • 청크 프로세스를 전용으로 처리하는 Tasklet이 존재하기 때문이다
  • 청크 커밋 직전에 StepExecution 의 apply 메서드를 호출하여 상태를 업데이트 함
  • ExitStatus 의 기본 종료코드 외 사용자 정의 종료코드를 생성해서 적용 할 수 있음

2.구조

  • readCount: 성공적으로 read한 아이템 수
  • writeCount: 성공적으로 write한 아이템 수
  • filterCount: temProcessor 에 의해 필터링된 아이템 수
  • parentSkipCount: 부모 클래스인 StepExecution 의 총 skip 횟수
  • readSkipCount: read에 실패해서 스킵된 횟수
  • writeSkipCount: write에 실패해서 스킵된 횟수
  • processSkipCount: process에 실패해서 스킵된 횟수
  • ExitStatus: 실행결과를 나타내는 클래스로서 종료코드를 포함(UNKNOWN, EXECUTING, COMPLETED, NOOP, FAILED, STOPPED)
  • StepExecution: StepExecution 객체 저장

StepContribution이 StepExecution에 업데이트 하는 절차

  1. TaskletStpe이 Create()를 통해 StepExecution을 생성한다.
  2. StepExcution이 create()를 통해 StepContribution을 생성한다.
  3. Chunk 기반의 프로세스를 처리할 수 있는 ChunkOrientedTasklet를 생성한다.
    1. ChunkOrientedTasklet은 기본적으로 Spring에서 제공한다.
    2. ChunckOriendTasklet을 상속해서 자신의 로직의 Task를 처리할 수 있게 구현할 수 있다.
    3. ItemReader, ItemProcessor, ItemWrite를 활용해서 Chunk를 처리한다.
  4. 청크 프로세스의 변경 사항을 StepContribution에 버퍼링한다.
  5. StepExecution이 완료되는 시점에 StepContribution에서 apply 메서드를 호출하여 속성들의 상태를 StepExecution에 적용한다.

ExecutionContext

1. 기본개념

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

2.구조

  • 위의 그림에서 ExecutionContext를 통해 Put한 데이터는 밑의 테이블인 short_context 컬럼에 저장된다.

예제 코드

//1.executionContextconfiguration
package com.example.springbatch_3_1_8_ExecutionContext;

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;

@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;
    private final ExecutionContextTasklet4 executionContextTasklet4;
    @Bean
    public Job BatchJob() {
        return this.jobBuilderFactory.get("Job")
                .start(step1())
                .next(step2())
                .next(step3())
                .next(step4())
                .build();
    }

    @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();
    }
}
//ExecutionContextTasklet
package com.example.springbatch_3_1_8_ExecutionContext;

import org.springframework.batch.core.JobInstance;
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 {
    @Override
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {

        System.out.println("Step1 was 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;
    }
}
//ExecutioncontextTasklet2
package com.example.springbatch_3_1_8_ExecutionContext;

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 {
    @Override
    public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
        System.out.println("Step2 was executed");

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

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

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

        //한번도 실행되지 않은 경우
        if (jobExecutionContext.get("stepName") == null) {
            jobExecutionContext.put("stepName", stepName);
        }
        return RepeatStatus.FINISHED;
    }
}
//ExecutionContextTasklet3
package com.example.springbatch_3_1_8_ExecutionContext;

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 {
    @Override
    public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
        System.out.println("Step3 was executed");
        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 com.example.springbatch_3_1_8_ExecutionContext;

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 {
    @Override
    public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {

        System.out.println("name : " + chunkContext.getStepContext().getStepExecution().getJobExecution().getExecutionContext().get("name"));
        System.out.println("step4 was executed");
        return RepeatStatus.FINISHED;
    }
}
  • 테스트 순서
    1. 먼저 수행을 해본다. 수행을 하게 된다면 아래와 같은 결과가 나온다.
/Users/namhyeop/Library/Java/JavaVirtualMachines/openjdk-17.0.1/Contents/Home/bin/java -XX:TieredStopAtLevel=1 -noverify -Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=63439:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Users/namhyeop/Desktop/git자료/Spring_Boot_Study/8.Spring_Batch/SpringBatch_3_1_8 ExecutionContext/target/classes:/Users/namhyeop/.m2/repository/org/springframework/boot/spring-boot-starter-batch/2.7.1/spring-boot-starter-batch-2.7.1.jar:/Users/namhyeop/.m2/repository/org/springframework/boot/spring-boot-starter/2.7.1/spring-boot-starter-2.7.1.jar:/Users/namhyeop/.m2/repository/org/springframework/boot/spring-boot/2.7.1/spring-boot-2.7.1.jar:/Users/namhyeop/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/2.7.1/spring-boot-autoconfigure-2.7.1.jar:/Users/namhyeop/.m2/repository/org/springframework/boot/spring-boot-starter-logging/2.7.1/spring-boot-starter-logging-2.7.1.jar:/Users/namhyeop/.m2/repository/ch/qos/logback/logback-classic/1.2.11/logback-classic-1.2.11.jar:/Users/namhyeop/.m2/repository/ch/qos/logback/logback-core/1.2.11/logback-core-1.2.11.jar:/Users/namhyeop/.m2/repository/org/apache/logging/log4j/log4j-to-slf4j/2.17.2/log4j-to-slf4j-2.17.2.jar:/Users/namhyeop/.m2/repository/org/apache/logging/log4j/log4j-api/2.17.2/log4j-api-2.17.2.jar:/Users/namhyeop/.m2/repository/org/slf4j/jul-to-slf4j/1.7.36/jul-to-slf4j-1.7.36.jar:/Users/namhyeop/.m2/repository/jakarta/annotation/jakarta.annotation-api/1.3.5/jakarta.annotation-api-1.3.5.jar:/Users/namhyeop/.m2/repository/org/yaml/snakeyaml/1.30/snakeyaml-1.30.jar:/Users/namhyeop/.m2/repository/org/springframework/boot/spring-boot-starter-jdbc/2.7.1/spring-boot-starter-jdbc-2.7.1.jar:/Users/namhyeop/.m2/repository/com/zaxxer/HikariCP/4.0.3/HikariCP-4.0.3.jar:/Users/namhyeop/.m2/repository/org/springframework/batch/spring-batch-core/4.3.6/spring-batch-core-4.3.6.jar:/Users/namhyeop/.m2/repository/com/fasterxml/jackson/core/jackson-databind/2.13.3/jackson-databind-2.13.3.jar:/Users/namhyeop/.m2/repository/com/fasterxml/jackson/core/jackson-annotations/2.13.3/jackson-annotations-2.13.3.jar:/Users/namhyeop/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.13.3/jackson-core-2.13.3.jar:/Users/namhyeop/.m2/repository/io/micrometer/micrometer-core/1.9.1/micrometer-core-1.9.1.jar:/Users/namhyeop/.m2/repository/org/hdrhistogram/HdrHistogram/2.1.12/HdrHistogram-2.1.12.jar:/Users/namhyeop/.m2/repository/org/latencyutils/LatencyUtils/2.0.3/LatencyUtils-2.0.3.jar:/Users/namhyeop/.m2/repository/javax/batch/javax.batch-api/1.0/javax.batch-api-1.0.jar:/Users/namhyeop/.m2/repository/org/codehaus/jettison/jettison/1.2/jettison-1.2.jar:/Users/namhyeop/.m2/repository/org/springframework/batch/spring-batch-infrastructure/4.3.6/spring-batch-infrastructure-4.3.6.jar:/Users/namhyeop/.m2/repository/org/springframework/retry/spring-retry/1.3.3/spring-retry-1.3.3.jar:/Users/namhyeop/.m2/repository/org/springframework/spring-aop/5.3.21/spring-aop-5.3.21.jar:/Users/namhyeop/.m2/repository/org/springframework/spring-beans/5.3.21/spring-beans-5.3.21.jar:/Users/namhyeop/.m2/repository/org/springframework/spring-context/5.3.21/spring-context-5.3.21.jar:/Users/namhyeop/.m2/repository/org/springframework/spring-expression/5.3.21/spring-expression-5.3.21.jar:/Users/namhyeop/.m2/repository/org/springframework/spring-tx/5.3.21/spring-tx-5.3.21.jar:/Users/namhyeop/.m2/repository/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar:/Users/namhyeop/.m2/repository/org/springframework/spring-core/5.3.21/spring-core-5.3.21.jar:/Users/namhyeop/.m2/repository/org/springframework/spring-jcl/5.3.21/spring-jcl-5.3.21.jar:/Users/namhyeop/.m2/repository/org/springframework/spring-jdbc/5.3.21/spring-jdbc-5.3.21.jar:/Users/namhyeop/.m2/repository/com/h2database/h2/2.1.214/h2-2.1.214.jar:/Users/namhyeop/.m2/repository/org/projectlombok/lombok/1.18.24/lombok-1.18.24.jar:/Users/namhyeop/.m2/repository/mysql/mysql-connector-java/8.0.29/mysql-connector-java-8.0.29.jar com.example.springbatch_3_1_8_ExecutionContext.SpringBatch33InstanceApplication
OpenJDK 64-Bit Server VM warning: Options -Xverify:none and -noverify were deprecated in JDK 13 and will likely be removed in a future release.

  .   ____          _            __ _ _
 /\\\\ / ___'_ __ _ _(_)_ __  __ _ \\ \\ \\ \\
( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\
 \\\\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.7.1)

2022-07-14 14:46:11.667  INFO 75459 --- [           main] c.e.s.SpringBatch33InstanceApplication   : Starting SpringBatch33InstanceApplication using Java 17.0.1 on NamHyeop.local with PID 75459 (/Users/namhyeop/Desktop/git자료/Spring_Boot_Study/8.Spring_Batch/SpringBatch_3_1_8 ExecutionContext/target/classes started by namhyeop in /Users/namhyeop/Desktop/git자료/Spring_Boot_Study/8.Spring_Batch/SpringBatch_3_1_8 ExecutionContext)
2022-07-14 14:46:11.667  INFO 75459 --- [           main] c.e.s.SpringBatch33InstanceApplication   : The following 1 profile is active: "mysql"
Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
2022-07-14 14:46:11.914  INFO 75459 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2022-07-14 14:46:11.915  WARN 75459 --- [           main] com.zaxxer.hikari.util.DriverDataSource  : Registered driver with driverClassName=com.mysql.jdbc.Driver was not found, trying direct instantiation.
2022-07-14 14:46:12.063  INFO 75459 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2022-07-14 14:46:12.304  INFO 75459 --- [           main] o.s.b.c.r.s.JobRepositoryFactoryBean     : No database type set, using meta data indicating: MYSQL
2022-07-14 14:46:12.335  INFO 75459 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : No TaskExecutor has been set, defaulting to synchronous executor.
2022-07-14 14:46:12.375  INFO 75459 --- [           main] c.e.s.SpringBatch33InstanceApplication   : Started SpringBatch33InstanceApplication in 0.834 seconds (JVM running for 1.036)
2022-07-14 14:46:12.376  INFO 75459 --- [           main] o.s.b.a.b.JobLauncherApplicationRunner   : Running default command line with: []
2022-07-14 14:46:12.435  INFO 75459 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [SimpleJob: [name=Job]] launched with the following parameters: [{}]
2022-07-14 14:46:12.477  INFO 75459 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [step1]
Step1 was executed
jobName : Job
stepName : step1
2022-07-14 14:46:12.513  INFO 75459 --- [           main] o.s.batch.core.step.AbstractStep         : Step: [step1] executed in 36ms
2022-07-14 14:46:12.547  INFO 75459 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [step2]
Step2 was executed
jobName = Job
stepName = null
2022-07-14 14:46:12.571  INFO 75459 --- [           main] o.s.batch.core.step.AbstractStep         : Step: [step2] executed in 24ms
2022-07-14 14:46:12.606  INFO 75459 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [step3]
Step3 was executed
2022-07-14 14:46:12.623 ERROR 75459 --- [           main] o.s.batch.core.step.AbstractStep         : Encountered an error executing step step3 in job Job

java.lang.RuntimeException: step2 was failed
	at com.example.springbatch_3_1_8_ExecutionContext.ExecutionContextTasklet3.execute(ExecutionContextTasklet3.java:17) ~[classes/:na]
	at org.springframework.batch.core.step.tasklet.TaskletStep$ChunkTransactionCallback.doInTransaction(TaskletStep.java:407) ~[spring-batch-core-4.3.6.jar:4.3.6]
	at org.springframework.batch.core.step.tasklet.TaskletStep$ChunkTransactionCallback.doInTransaction(TaskletStep.java:331) ~[spring-batch-core-4.3.6.jar:4.3.6]
	at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:140) ~[spring-tx-5.3.21.jar:5.3.21]
	at org.springframework.batch.core.step.tasklet.TaskletStep$2.doInChunkContext(TaskletStep.java:273) ~[spring-batch-core-4.3.6.jar:4.3.6]
	at org.springframework.batch.core.scope.context.StepContextRepeatCallback.doInIteration(StepContextRepeatCallback.java:82) ~[spring-batch-core-4.3.6.jar:4.3.6]
	at org.springframework.batch.repeat.support.RepeatTemplate.getNextResult(RepeatTemplate.java:375) ~[spring-batch-infrastructure-4.3.6.jar:4.3.6]
	at org.springframework.batch.repeat.support.RepeatTemplate.executeInternal(RepeatTemplate.java:215) ~[spring-batch-infrastructure-4.3.6.jar:4.3.6]
	at org.springframework.batch.repeat.support.RepeatTemplate.iterate(RepeatTemplate.java:145) ~[spring-batch-infrastructure-4.3.6.jar:4.3.6]
	at org.springframework.batch.core.step.tasklet.TaskletStep.doExecute(TaskletStep.java:258) ~[spring-batch-core-4.3.6.jar:4.3.6]
	at org.springframework.batch.core.step.AbstractStep.execute(AbstractStep.java:208) ~[spring-batch-core-4.3.6.jar:4.3.6]
	at org.springframework.batch.core.job.SimpleStepHandler.handleStep(SimpleStepHandler.java:152) ~[spring-batch-core-4.3.6.jar:4.3.6]
	at org.springframework.batch.core.job.AbstractJob.handleStep(AbstractJob.java:413) ~[spring-batch-core-4.3.6.jar:4.3.6]
	at org.springframework.batch.core.job.SimpleJob.doExecute(SimpleJob.java:136) ~[spring-batch-core-4.3.6.jar:4.3.6]
	at org.springframework.batch.core.job.AbstractJob.execute(AbstractJob.java:320) ~[spring-batch-core-4.3.6.jar:4.3.6]
	at org.springframework.batch.core.launch.support.SimpleJobLauncher$1.run(SimpleJobLauncher.java:149) ~[spring-batch-core-4.3.6.jar:4.3.6]
	at org.springframework.core.task.SyncTaskExecutor.execute(SyncTaskExecutor.java:50) ~[spring-core-5.3.21.jar:5.3.21]
	at org.springframework.batch.core.launch.support.SimpleJobLauncher.run(SimpleJobLauncher.java:140) ~[spring-batch-core-4.3.6.jar:4.3.6]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344) ~[spring-aop-5.3.21.jar:5.3.21]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198) ~[spring-aop-5.3.21.jar:5.3.21]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.3.21.jar:5.3.21]
	at org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration$PassthruAdvice.invoke(SimpleBatchConfiguration.java:128) ~[spring-batch-core-4.3.6.jar:4.3.6]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.21.jar:5.3.21]
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215) ~[spring-aop-5.3.21.jar:5.3.21]
	at jdk.proxy2/jdk.proxy2.$Proxy50.run(Unknown Source) ~[na:na]
	at org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner.execute(JobLauncherApplicationRunner.java:199) ~[spring-boot-autoconfigure-2.7.1.jar:2.7.1]
	at org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner.executeLocalJobs(JobLauncherApplicationRunner.java:173) ~[spring-boot-autoconfigure-2.7.1.jar:2.7.1]
	at org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner.launchJobFromProperties(JobLauncherApplicationRunner.java:160) ~[spring-boot-autoconfigure-2.7.1.jar:2.7.1]
	at org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner.run(JobLauncherApplicationRunner.java:155) ~[spring-boot-autoconfigure-2.7.1.jar:2.7.1]
	at org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner.run(JobLauncherApplicationRunner.java:150) ~[spring-boot-autoconfigure-2.7.1.jar:2.7.1]
	at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:762) ~[spring-boot-2.7.1.jar:2.7.1]
	at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:752) ~[spring-boot-2.7.1.jar:2.7.1]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) ~[spring-boot-2.7.1.jar:2.7.1]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) ~[spring-boot-2.7.1.jar:2.7.1]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295) ~[spring-boot-2.7.1.jar:2.7.1]
	at com.example.springbatch_3_1_8_ExecutionContext.SpringBatch33InstanceApplication.main(SpringBatch33InstanceApplication.java:12) ~[classes/:na]

2022-07-14 14:46:12.627  INFO 75459 --- [           main] o.s.batch.core.step.AbstractStep         : Step: [step3] executed in 21ms
2022-07-14 14:46:12.650  INFO 75459 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [SimpleJob: [name=Job]] completed with the following parameters: [{}] and the following status: [FAILED] in 199ms
2022-07-14 14:46:12.652  INFO 75459 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2022-07-14 14:46:12.663  INFO 75459 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

Process finished with exit code 0
  • 테스트에서 Step1과 Step2는 변수가 공유가 안된다는것을 확인할 수 있다.
  • 그러나 Job의 정보는 공유가 된다는것을 확인할 수 있다.
  • Step3에서 예외가 발생하게 되고 예외 발생 조건을 해결하게 진행하도록 테스트 코드를 작성했다. 그렇기 때문에 다음 수행에서는 예외가 발생하지 않고 Step4부터 수행이 시작되어 정상적으로 마무리 되는것을 확인할 수 있다.

JobRepository

  1. 기본개념
  • 배치 작업 중의 정보를 저장하는 저장소 역할
  • Job이 언제 수행되었고, 언제 끝났으며, 몇 번이 실행되었고 실행에 대한 결과 등의 배치 작업의 수행과 관련된 모든 meta data 를 저장함
  • JobLauncher, Job, Step 구현체 내부에서 CRUD 기능을 처리함

JobRepository의 메소드

  • JobRepository 설정
    • @EnableBatchProcessing 어노테이션만 선언하면 JobRepository 가 자동으로 빈으로 생성됨
    • BatchConfigurer 인터페이스를 구현하거나 BasicBatchConfigurer 를 상속해서 JobRepository 설정을 커스터마이징 할 수 있다
      • 설정 방식은 JDBC, InMememory 방식 2가지가 있다.
      • JDBC 방식으로 설정- JobRepositoryFactoryBean
        • 내부적으로 AOP 기술를 통해 트랜잭션 처리를 해주고 있음
        • 트랜잭션 isolation 의 기본값은 SERIALIZEBLE 로 최고 수준, 다른 레벨(READ_COMMITED, REPEATABLE_READ)로 지정 가능
        • 메타테이블의 Table Prefix 를 변경할 수 있음, 기본 값은 “BATCH_” 임
      • In Memory 방식으로 설정 – MapJobRepositoryFactoryBean
        • 성능 등의 이유로 도메인 오브젝트를 굳이 데이터베이스에 저장하고 싶지 않을 경우
        • 보통 Test 나 프로토타입의 빠른 개발이 필요할 때 사용

JobLauncher

1.기본개념

  • 배치 Job 을 실행시키는 역할을 한다
  • Job과 Job Parameters를 인자로 받으며 요청된 배치 작업을 수행한 후 최종 client 에게 JobExecution을 반환함
  • 스프링 부트 배치가 구동이 되면 JobLauncher 빈이 자동 생성 된다
  • Job 실행
    • JobLanucher.run(Job, JobParameters)
    • 스프링 부트 배치에서는 JobLauncherApplicationRunner 가 자동적으로 JobLauncher 을 실행시킨다
    • 동기적 실행
      • taskExecutor 를 SyncTaskExecutor 로 설정할 경우 (기본값은 SyncTaskExecutor)
      • JobExecution 을 획득하고 배치 처리를 최종 완료한 이후 Client 에게 JobExecution 을 반환
      • 스케줄러에 의한 배치처리에 적합 함 – 배치처리시간이 길어도 상관없는 경우(클라이언트가 오래 기다릴 일이 없는 경우) → 하루 정산 차트를 계산해야하는 경우
    • 비 동기적 실행
      • taskExecutor 가 SimpleAsyncTaskExecutor 로 설정할 경우
        • SimpleAsyncTaskExecutor: SpringBatch에서 제공하는 TaskExecutor의 비동기적 실행을 위한 클래스를 의미한다.
      • JobExecution 을 획득한 후 Client 에게 바로 JobExecution 을 반환하고 배치처리를 완료한다
      • HTTP 요청에 의한 배치처리에 적합함–배치 처리 시간이 길 경우 응답이 늦어지지 않도록 함

2.구조

예제코드

//JobLauncherConfiguration.java
package com.example.springbatch_3_1_3_instance;

import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.Job;
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.launch.support.RunIdIncrementer;
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;

/**
 * RunIdIncreMenter는 해당 파라미터로 여러번 실행을하고 싶을때 사용한다.
 * RunIdIncreMenter를 사용할 경우 Job Parameter외에 run.id라는 임의의 파라미터를 추가로 사용해서 값을 변경해준다.
 * 매 실행마다 run.id가 변경되어 재실행 가능한것이다.
 */
@RequiredArgsConstructor
@Configuration
public class JobLauncherConfiguration {
    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job BatchJob() {
        return this.jobBuilderFactory.get("Job")
                .start(step1())
                .next(step2())
                .incrementer(new RunIdIncrementer())
                .build();
    }
    
    @Bean
    public Step step1() {
        return stepBuilderFactory.get("step1")
                .tasklet(new Tasklet() {
                    @Override
                    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
                        Thread.sleep(3000); //비동기, 동기 테스트를 시험하기 위한 Thread.sleep 옵션
                        return RepeatStatus.FINISHED;
                    }
                })
                .build();
    }
    @Bean
    public Step step2() {
        return stepBuilderFactory.get("step2")
                .tasklet((contribution, chunkContext) -> null)
                .build();
    }
}
//JobLauncherController.java
package com.example.springbatch_3_1_3_instance;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.support.SimpleJobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.batch.BasicBatchConfigurer;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;

@RestController
public class JobLauncherController {
    @Autowired
    private Job job;

    @Autowired private JobLauncher jobLauncher;

    @Autowired
    private BasicBatchConfigurer basicBatchConfigurer;

    @PostMapping("/batch")
    public String launch(@RequestBody Member member) throws Exception {
        JobParameters jobParameters = new JobParametersBuilder()
                .addString("id", member.getId())
                .addDate("date", new Date())
                .toJobParameters();

        SimpleJobLauncher jobLauncher = (SimpleJobLauncher) basicBatchConfigurer.getJobLauncher();
        /**
         * 아래 주석처리한 방식은 불가능함. Spring이 proxy객체로 생성하기 때문에 타입캐스팅이 불가능함
         */
//        SimpleJobLauncher jobLauncher = (SimpleJobLauncher) basicBatchConfigurer;
        /**
         * 비동기 설정 옵션. 기본 값은 동기로 실행된다.
         */
        jobLauncher.setTaskExecutor(new SimpleAsyncTaskExecutor());
        jobLauncher.run(job, jobParameters);
        return "batch completed";
    }
}
//Member.java
package com.example.springbatch_3_1_3_instance;

import lombok.Data;

@Data
public class Member {
    private String id;
}
//batch.http(Post 요청 파일)
### Send POST request with json body
POST <http://localhost:8080/batch>
Content-Type: application/json

{
"id":"leaven"
}

1.JobLauncherController에서 JobLauncher의 비동기, 동기 설정을 사용하여 Job이 어떻게 처리되는지를 확인하는 예제이다.

2.먼저 setTaskExecutor를 사용하지 않고 테스트를 진행해보면 Step에 sleep를 설정했기 때문에 처리 시간이 비동기 테스트 보다 느리다. 시간 차이를 확인해보자

//동기 결과 - 3281ms 소요
<http://localhost:8080/batch>

HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 15
Date: Fri, 15 Jul 2022 00:43:48 GMT
Keep-Alive: timeout=60
Connection: keep-alive

batch completed

Response code: 200; Time: 3281ms; Content length: 15 bytes
//비동기 결과 - 135ms 소요
<http://localhost:8080/batch>

HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 15
Date: Fri, 15 Jul 2022 00:42:50 GMT
Keep-Alive: timeout=60
Connection: keep-alive

batch completed

Response code: 200; Time: 135ms; Content length: 15 bytes
반응형

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

6.Spring Batch의 Flow  (1) 2024.11.07
5.Spring Batch의 Step  (0) 2024.11.07
4.Spring Batch의 Job  (0) 2024.11.06
2.Spring Batch 시작하기  (0) 2024.11.02
1.Spring Batch 소개  (0) 2022.07.04