4장. 잡과 스텝 이해하기
잡은 처음부터 끝까지 독립적으로 실행할 수 있는 고유하며 순서가 지정된 여러 스텝의 목록이다.
잡의 생명주기
JobRunner: Job 실행의 시작
CommandLineJobRunner
스크립트, CLI 를 통한 잡의 실행
JobRegistryBackgroundJobRunner
스케줄러를 통해 잡 실행 시 스프링이 부트스트랩 될 때 실행 가능한 JobRegistry를 생성하는데, JobRegistry를 생성하는 러너
JobLauncherCommandLineRunner
기본적으로 모든 Job타입의 빈을 기동시에 실행(@deprecated since 2.3.0 in favor of {@link JobLauncherApplicationRunner})
위 JobRunner는 표준이아니며 실제로 프레임워크 실행 시 구동되는 것은 JobLanucher
SimpleJobLauncher: 스프링에서 제공하는 기본 Launcher
- 내부적으로 TaskExecutor를 사용하고있음, SyncTaskExecutor 를 사용하면 JobLauncher와 동일한 스레드에서 잡이 실행
하나의 Job 구성이 파라미터가 달라지며 실행되면 JobInstance가 생성되고 JobExecution 이 성공적으로 완료된 상태이면 JobInstance는 완료되었다고 간주한다.
잡 구성하기
@EnableBatchProcessing
애플리케이션 내에서 한 번만 적용하면 되며 배치. 수행에 필요한 인프라스트럭처를 제공함
Bom-batch 배치 프로젝트에서 사용하는 공용 configuration 하나에만 해당 애너테이션을 작성
@Configuration
@EnableBatchProcessing // 애플리케이션 내에서 한 번만 적용됨
@ComponentScan(value = {
"com.javabom.definitiveguide.chap2",
"com.javabom.definitiveguide.chap13",
"com.javabom.definitiveguide.chap4"
})
public class DefiniteBatchConfig {
}
com.javabom.definitiveguide.chap4.job.HelloWorldConfiguration.java
@Configuration
@RequiredArgsConstructor
public class HelloWorldConfiguration {
private static final String JOB_NAME = "chap4_job";
/**
* 실제로 스프링 배치의 잡과 스텝을 생성하는데 사용되는 JobBuilder, StepBuilder 인스턴스 생성
*/
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
@Bean(name = JOB_NAME)
public Job job() {
return this.jobBuilderFactory.get(JOB_NAME) // Job 의 이름 지정
.start(step1())
.build(); // 최종적으로 SimpleJob 생성
}
@Bean(name = JOB_NAME + "-step1")
public Step step1() {
return stepBuilderFactory.get(JOB_NAME + "-step1")
.tasklet(((contribution, chunkContext) -> { // tasklet 을 사용하는 스텝
System.out.println("Hello, World!");
return RepeatStatus.FINISHED;
})).build();
}
}
잡 파라미터
동일한 식별 파라미터로 잡을 두 번 이상 실행할 수 없음
잡에 파라미터를 전달하는 방법
스프링배치는 잡에 파라미터를 전달하거나 파라미터를 자동 증가시키거나 검증할 수 있도록 함
JobRunner는 JobParameters 객체를 생성해 JobInstace에 전달
java -jar definitive-guide/build/libs/definitive-guide.jar --spring.batch.job.names=chap4_job foo=bar
스프링배치에서는 기본적으로 파라미터 타입을 변환하는 기능을 제공한다.
CommandLineJobRunner#start
JobParameters jobParameters = jobParametersConverter.getJobParameters(StringUtils
.splitArrayElementsIntoProperties(parameters, "="));
DefaultJobParametersConverter
public static final String DATE_TYPE = "(date)";
public static final String STRING_TYPE = "(string)";
public static final String LONG_TYPE = "(long)";
private static final String DOUBLE_TYPE = "(double)";
사용예시
java -jar definitive-guide/build/libs/definitive-guide.jar --spring.batch.job.names=chap4_job foo(string)=bar
파라미터가 식별에 사용되지 않도록 하는 예시
name=BOM 파라미터는 JobInstance 식별에 사용되지않음
java -jar definitive-guide/build/libs/definitive-guide.jar --spring.batch.job.names=chap4_job date=2021-10-16 -name=BOM
첫번째 잡이 실패했을 때 name=BOM 의 값이 변경되어도 date 값이 동일하면 기존 JobInstance 를 기반으로 새 JobExecution 을 생성함
잡 파라미터에 접근하기
ChunkContext 사용
private Tasklet helloWorldTasklet() {
/**
* contribution: StepContribution
* 아직 커밋되지 않은 현재 트랜잭션에 대한 정보(읽기수, 읽기수)
*
* chunkContext: ChunkContext
* 실행 시점의 잡 상태
* 태스크릿 내의 처리중인 청크와 관련된 정보, 청크 정보 중에는 스텝, 잡 정보도 있음
*/
return (contribution, chunkContext) -> { // tasklet 을 사용하는 스텝
String name = (String)chunkContext.getStepContext() // chunkContext에서 파라미터 값 얻기
.getJobParameters()
.get("name");
System.out.println("Hello, World! " + name);
return RepeatStatus.FINISHED;
};
}
Late binding
JobScope 이나 StepScop 빈에선은 late binding 을 통해 jobParameters 를 직접 참조하지 않고도 파라미터 값을 얻어올 수 있음
@Bean(name = JOB_NAME + "-step2")
@JobScope
public Step step2(@Value("#{jobParameters['name']}") String name) {
return stepBuilderFactory.get(JOB_NAME + "-step2")
.tasklet(((contribution, chunkContext) -> {
System.out.println("Step2 : " + name);
return RepeatStatus.FINISHED;
}))
.build();
}
빈 생성 시점을 Job 실행시점 또는 Step 실행 시점으로 미루어서 JobParameter를 빈 생성 시에 바인딩 할 수 있도록 한다.
잡 파라미터 유효성 검증: JobParametersValidator
스프링 배치가 제공하는 유효성 검증 인터페이스
throws JobParametersInvalidException 발생 유무로 파라미터의 유효성 판단
@Component
public class NameFormatValidator implements JobParametersValidator {
@Override
public void validate(JobParameters parameters) throws JobParametersInvalidException {
if (Objects.isNull(parameters)) {
// void 타입이며 해당 Exception 유무로 유효성 판단
throw new JobParametersInvalidException("parameter is empty");
}
JobParameter name = parameters.getParameters().get("name");
if (Objects.isNull(name) || name.getType() != JobParameter.ParameterType.STRING) {
throw new JobParametersInvalidException("type is not null and not string");
}
}
}
@Bean(name = JOB_NAME)
public Job job() {
return this.jobBuilderFactory.get(JOB_NAME) // Job 의 이름 지정
.validator(nameFormatValidator)
.start(step1())
.next(step2(null))
.build(); // 최종적으로 SimpleJob 생성
}
Caused by: org.springframework.batch.core.JobParametersInvalidException: type is not null and not string
스프링 배치가 제공하는 기본 유효성 검증기
DefaultJobParametersValidator
.validator(new DefaultJobParametersValidator(new String[]{"name"}, new String[]{"date"}))
Caused by: org.springframework.batch.core.JobParametersInvalidException: The JobParameters do not contain required keys: [name]
required와 optional 둘다 없는 파라미터가 들어가면 Exception 발생
Caused by: org.springframework.batch.core.JobParametersInvalidException: The JobParameters contains keys that are not explicitly optional or required: [datedd]
CompositeJobParametersValidator
두 개 이상의 유효성 검증기 사용
@Bean
public CompositeJobParametersValidator compositeJobParametersValidator() {
CompositeJobParametersValidator compositeJobParametersValidator = new CompositeJobParametersValidator();
compositeJobParametersValidator.setValidators(Arrays.asList(defaultJobParametersValidator(), nameFormatValidator));
compositeJobParametersValidator.afterPropertiesSet();
return compositeJobParametersValidator;
}
잡 파라미터 증가시키기: JobParameterIncrementer
기본적으로 제공하는 RunIdIncrementer
return new JobParametersBuilder(params).addLong(this.key, id).toJobParameters();
잡 리스너 적용하기
잡 실행과 관련있는 리스너: JobExecutionListener
어노테이션을 통해 해당 리스너 인터페이스를 직접 구현하지 않을 수도 잆음
public class JobLoggerListener {
/**
* Expected signature: void beforeJob({@link JobExecution} jobExecution)
* @param jobExecution
*/
@BeforeJob
public void beforeJob(JobExecution jobExecution) {
System.out.println(jobExecution.getJobInstance().getJobName() + "is started");
}
/**
* Expected signature: void afterJob({@link JobExecution} jobExecution)
*/
@AfterJob
public void afterJob(JobExecution jobExecution) {
System.out.println(jobExecution.getJobInstance().getJobName() + " has completed with the status "
+ jobExecution.getStatus());
}
}
ExecutionContext
JobExecution ----------------> ExeutionContext (잡 전체용 글로벌 데이터)
|
|
|1..*
StepExeciton ------------------> ExecutionContext (개별 스텝용 데이터)
job context 조작하기 HelloWorldJobContext.java
/**
* job 글로벌 Context
*/
ExecutionContext jobContext = chunkContext.getStepContext()
.getStepExecution()
.getJobExecution()
.getExecutionContext();
if (Objects.isNull(jobContext.get("user.name"))) {
log.info("jobContext user.name is empty, start put user.name: " + name);
jobContext.put("user.name", name); // job 글로벌 Context에 저장
} else {
log.info("jobContext user.name is not empty");
}
step 개별 Context 의 값을 job context로 승격하기
/**
* step 이 성공적으로 마치면 job context에 step context 내용을 저장함
* @return
*/
public StepExecutionListener promotionListner() {
ExecutionContextPromotionListener listener = new ExecutionContextPromotionListener();
listener.setKeys(new String[] {"step.name"});
return listener;
}
database 테이블에 저장된 내용
BATCH_STEP_EXECUTION_CONTEXT
{"@class":"java.util.HashMap","batch.taskletType":"com.javabom.definitiveguide.chap4.job.HelloWorldStepContext","step.name":"bom","batch.stepType":"org.springframework.batch.core.step.tasklet.TaskletStep"}
BATCH_JOB_EXECUTION_CONTEXT
{"@class":"java.util.HashMap","user.name":"bom"}
'Reading Record > 스프링 배치 완벽 가이드' 카테고리의 다른 글
6장 잡 실행하기 (0) | 2021.11.15 |
---|---|
5장 JobRepository와 메타데이터 (0) | 2021.10.30 |
4장 잡과 스텝 이해하기 : 스텝 알아보기 (0) | 2021.10.24 |
13장 배치 처리 테스트하기 (0) | 2021.10.12 |
2장 스프링 배치 (0) | 2021.10.03 |