스프링 배치의 실행 및 관리 목적의 도메인(Job, Step..) 정보들을 저장, 업데이트, 조회할 수 있는 스키마를 제공
DB 와 연동할 경우 필수적으로 메타 테이블이 생성되어있어야 한다.
스키마 파일 위치 : /org/springframework/batch/core/schema-*.sql
spring.batch.jdbc.initialize-schema
설정어플리케이션 실행 시 주입
java -jar LogBatch.jar requestDate=20210101
코드로 생성
JobParameterBuilder
, DefaultJobParameterConverter
SpEL 이용
@Value(”#{jobParameter[requestDate]}”)
, @JobScope
, @StepScope
선언 필수
JobParameter 를 코드에서 참조방법
@Bean
public Step step1() {
return stepBuilderFactory.get("step1")
.tasklet((contribution, chunkContext) -> {
// contribution 에서 참조
JobParameters jobParameters = contribution.getStepExecution().getJobParameters();
// chunkContext 에서 참조해서 파라미터 값 확인
Map<String, Object> jobParameters1 = chunkContext.getStepContext().getJobParameters();
return RepeatStatus.FINISHED;
})
.build();
}
JobInstance
에 대한 한번의 시도를 의미하는 객체Job
실행 중에 발생한 정보들을 저장하고 있는 객체 JobExecution
은 FAILED
또는 COMPLETED
등의 Job
실행 결과 상태를 가지고 있음COMPLETED
면 재실행이 불가함FAILED
면 재실행이 가능함 JobExecution
의 실행 결과 상태가 COMPLETED
가 될 때 까지 JobInstance
내에서 여러 번의 시도가 생길 수 있다.Step 범위 : 각 Step 의 StepExecution 에 저장되며 Step 간 서로 공유 안됨
Job 범위 : 각 Job 의 JobExecution 에 저장되며 Job 간 서로 공유 안되며 해당 Job 의 Step 간 서로 공유됨
실패한 Job 재 시작 시 이미 처리한 Row 데이터는 건너뛰고 이후부터 수행할 수 있도록 할 때 ExecutionContext 를 활용할 수 있다.
@EnableBatchProcessing
애노테이션 선언시 JobRepository 가 자동으로 빈으로 등록 BatchConfigurer
인터페이스를 구현하거나 BasicBatchConfgiurer
를 상속하여 JobRepository 설정 커스터마이징 가능JobLauncher
빈이 자동 생성된다. Job실행 JobLauncher.run(Job, Jobparameters)
JobLauncherApplicationRunner
spring:
batch:
job:
enabled: true
# (기본값 true 사용하고 싶지 않은경우 false 설정할 것)
spring:
batch:
job:
names: ${job.name:NONE}
--job.name=helloJob,simpleJob
jobBuilderFactory.get("jobName") // jobName 은 스프링 배치가 job 을 실행할 때 참조하는 job 이름
ChunkOrientedTasklet
구현체 제공public class PassCheckingLister implements StepExecutionListener {
@Override
public void beforeStep(StepExecution stepExecution) {
}
@Override
public ExitStatus afterStep(StepExecution stepExecution) {
String exitCode = stepExecution.getExitStatus().getExitCode();
if(!exitCode.equals(ExitStatus.FAILED.getExitCode())) {
return new ExitStatus("PASS");
}
return null;
}
}
@Bean
public Job batchJob() {
return jobBuilderFactory.get("job")
.incrementer(new RunIdIncrementer())
.start(step1())
.next(decider())
.from(decider()).on("ODD").to(oddStep())
.from(decider()).on("EVEN").to(eventStep())
.end()
.build();
}
@Bean
public JobExecutionDecider decider() {
return new CustomDecider();
}
====
public class CustomDecider implements JobExecutionDecider {
private int count = 0;
@Override
public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {
count++;
if(count % 2 == 0) {
return new FlowExecutionStatus("EVEN");
}
return new FlowExecutionStatus("ODD");
}
}
// jobConfig
@RequiredArgsConstructor
@Configuration
public class JobConfiguration {
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
private static final String ANY_STATUS = "*";
@Bean
public Job batchJob() {
return jobBuilderFactory.get("job")
.incrementer(new RunIdIncrementer())
.start(step1(null))
.next(step2())
.listener(new JobListener())
.build();
}
@Bean
@JobScope
public Step step1(@Value("#{jobParameters['message']}") String message) {
return stepBuilderFactory.get("step1")
.tasklet((contribution, chunkContext) -> {
System.out.println("message --> " + message);
System.out.println(contribution.getStepExecution().getStepName() + " has executed");
return RepeatStatus.FINISHED;
}).build();
}
@Bean
public Step step2() {
return stepBuilderFactory.get("step2")
.tasklet(tasklet(null, null, null))
.listener(new CustomStepExecutionListener())
.build();
}
@Bean
@StepScope
public Tasklet tasklet(@Value("#{jobExecutionContext['name']}") String name,
@Value("#{stepExecutionContext['name2']}") String name2,
@Value("#{jobParameters['message']}") String message) {
return (stepContribution, chunkContext) -> {
System.out.println("name --> " + name);
System.out.println("name2 --> " + name2);
System.out.println("message --> " + message);
System.out.println("tasklet has executed");
return RepeatStatus.FINISHED;
};
}
}
// JobListener
public class JobListener implements JobExecutionListener {
@Override
public void beforeJob(JobExecution jobExecution) {
jobExecution.getExecutionContext().put("name", "user1");
}
@Override
public void afterJob(JobExecution jobExecution) {
}
}
// StepListener
public class CustomStepExecutionListener implements StepExecutionListener {
@Override
public void beforeStep(StepExecution stepExecution) {
stepExecution.getExecutionContext().put("name2", "user2");
}
@Override
public ExitStatus afterStep(StepExecution stepExecution) {
return null;
}
}
뛰어난 글이네요, 감사합니다.