반응형
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)을 실행한다.
- 이때 DB를 조회해서 Job과 JobParameters가 존재할 경우에는 기존 JobInstance를 리턴한다.
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 은 실패한다
- 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에 업데이트 하는 절차
- TaskletStpe이 Create()를 통해 StepExecution을 생성한다.
- StepExcution이 create()를 통해 StepContribution을 생성한다.
- Chunk 기반의 프로세스를 처리할 수 있는 ChunkOrientedTasklet를 생성한다.
- ChunkOrientedTasklet은 기본적으로 Spring에서 제공한다.
- ChunckOriendTasklet을 상속해서 자신의 로직의 Task를 처리할 수 있게 구현할 수 있다.
- ItemReader, ItemProcessor, ItemWrite를 활용해서 Chunk를 처리한다.
- 청크 프로세스의 변경 사항을 StepContribution에 버퍼링한다.
- 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;
}
}
- 테스트 순서
- 먼저 수행을 해본다. 수행을 하게 된다면 아래와 같은 결과가 나온다.
/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
- 기본개념
- 배치 작업 중의 정보를 저장하는 저장소 역할
- 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 요청에 의한 배치처리에 적합함–배치 처리 시간이 길 경우 응답이 늦어지지 않도록 함
- taskExecutor 가 SimpleAsyncTaskExecutor 로 설정할 경우
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 |