Job
Job은 Spring Batch의 계층 구조에서 가장 상위에 위치한 개념으로, 하나의 배치 작업 그 자체를 의미한다.
Job은 최소 하나 이상의 Step으로 구성되며, Spring Batch가 정의한 Job 인터페이스를 구현해 빈으로 등록 후 배치 작업을 실행시킬 수 있다.
JobParamter
Job 자체는 동일한 논리적인 작업 그 자체이지만, 해당 Job 자체는 모두 독립적인 실행을 보장해야할 것이다.
예컨대 특정 사용자가 한 해 사용한 카드값을 정산하는 Job이 있다고 가정했을 때, 카드값을 계산하는 로직 자체는 누구에게나 동일할 것이지만 정산을 하는 시점 및 대상 등은 Job을 실행하는 시점에 따라 달라질 수 있다.
이러한 기능을 제공하기 위해서 Spring Batch에선 실제 Job을 실행할 때 JobParameter와 함께 JobInstance를 생성하고 관리하여 해당 Job이 고유한 것으로 취급될 수 있도록 한다.
아래처럼 JobParamter에 변하지 않는 특정 값을 설정해보자.
@Configuration
@RequiredArgsConstructor
public class JobLauncherConfiguration {
private final Job simpleJob;
private final JobLauncher jobLauncher;
@Bean
public ApplicationRunner jobLauncherWithParameter() {
return args -> {
JobParameters JobParamter = new JobParametersBuilder()
.addString("paramter", "1")
.toJobParameters();
jobLauncher.run(simpleJob, JobParamter);
};
}
}
이후, 해당 Job을 두 번 실행시키면
A job instance already exists and is complete for identifying parameters={'paramter':'{value=1, type=class java.lang.String, identifying=true}'}. If you want to run this job again, change the parameters.
위와 같이 해당 JobInstance가 이미 실행되었으므로, Job을 다시 한 번 실행시키고자 한다면 paramter를 변경하라는 메시지가 던져진다.
이러한 JobParameter는 특정 Job을 실행하는데 있어서 일종의 멱등키 역할을 할 수 있다.
만약 구성하고자 하는 Job이 어떠한 기준에 근거하여 중복으로 실행되선 안된다는 요구사항이 있다면 이 JobParamter를 적절히 구성해야할 것이다.
SimpleJob과 FlowJob
Spring Batch에선 기능적 측면에서 SimpleJob과 FlowJob을 Job 인터페이스의 기본 구현체로 제공한다.
순차 실행은 SimpleJob에게
SimpleJob 객체는 이름에서도 유추할 수 있듯 Job을 이루는 여러 Step들을 순차적으로 실행시켜준다.
아래 코드를 보자.
@Bean
public Job simpleJob() {
Job jobInstance = new JobBuilder("defaultJob", jobRepository)
.start(step1())
.next(step2())
.build();
return jobInstance;
}
@Bean
public Step step1() {
return new StepBuilder("step1", jobRepository)
.tasklet((contribution, chunkContext) -> {
System.out.println("step 1 executed");
return RepeatStatus.FINISHED;
}, transactionManager )
.build();
}
@Bean
public Step step2() {
return new StepBuilder("step2", jobRepository)
.tasklet((contribution, chunkContext) -> {
System.out.println("step 2 executed");
return RepeatStatus.FINISHED;
}, transactionManager )
.build();
}
SimpleJob이 step1을 시작하고, 그 다음엔 step2를 시작하도록 설정되어있다.
해당 Job을 실행시켜보면 다음과 같이 step1과 step2가 순차적으로 실행된 것을 확인할 수 있다.
INFO 39162 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step1]
step 1 executed
INFO 39162 --- [ main] o.s.batch.core.step.AbstractStep : Step: [step1] executed in 56ms
INFO 39162 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step2]
step 2 executed
INFO 39162 --- [ main] o.s.batch.core.step.AbstractStep : Step: [step2] executed in 42ms
만약 Step을 더 추가하고자 한다면 간단히 JobBuilder의 next()로 체이닝해주면 된다.
논리적 실행은 FlowJob에게
FlowJob은 SimpleJob과 다르게, 그것을 구성하는 여러 개의 Step들에 대하여 논리 구조를 추가할 수 있다.
예컨대 특정 Step이 성공하면 stepA를, 실패하면 stepB를 실행시키도록 설정할 수 있는 것이다.
해당 논리는 앞서 언급했던 on(), to() 등의 flow 및 transition 관련 API와 함께 구성할 수 있다.
아래와 같은 flowJob이 구성되어있다고 가정해보자.
@Bean
public Job flowJob() {
Job flowJob = new JobBuilder("flowJob", jobRepository)
.start(firstStep())
.on("COMPLETED").to(stepOnSuccess())
.from(firstStep())
.on("FAILED").to(stepOnFail())
.end()
.build();
return flowJob;
}
@Bean
public Step firstStep() {
FlowBuilder<Object> firstFlow = new FlowBuilder<>("firstFlow");
return new StepBuilder("firstStep", jobRepository)
.tasklet((contribution, chunkContext) -> {
System.out.println("firstStep executed");
return RepeatStatus.FINISHED;
}, transactionManager )
.build();
}
@Bean
public Step stepOnFail() {
return new StepBuilder("stepOnFail", jobRepository)
.tasklet((contribution, chunkContext) -> {
System.out.println("stepOnFail executed");
return RepeatStatus.FINISHED;
}, transactionManager )
.build();
}
@Bean
public Step stepOnSuccess() {
return new StepBuilder("stepOnSuccess", jobRepository)
.tasklet((contribution, chunkContext) -> {
System.out.println("stepOnSuccess executed");
return RepeatStatus.FINISHED;
}, transactionManager )
.build();
}
해당 FlowJob은 firstStep을 실행한 후, 해당 Step의 실행 결과에 따라 stepOnSuccess 혹은 stepOnFail을 실행시킨다.
우선 저대로 한 번 실행시켜보자.
firstStep executed
...
stepOnSuccess executed
firstStep이 문제없이 성공했으므로 stepOnSuccess가 실행된다.
이번엔 firstStep에서 예외를 발생시켜보자.
@Bean
public Step firstStep() {
FlowBuilder<Object> firstFlow = new FlowBuilder<>("firstFlow");
return new StepBuilder("firstStep", jobRepository)
.tasklet((contribution, chunkContext) -> {
System.out.println("firstStep executed");
// return RepeatStatus.FINISHED;
throw new RuntimeException();
}, transactionManager )
.build();
}
firstStep executed
ERROR 47179 --- [ main] o.s.batch.core.step.AbstractStep : Encountered an error executing step firstStep in job flowJob
java.lang.RuntimeException: null
...
stepOnFail executed
역시 stepOnFail이 잘 실행된 것을 확인할 수 있었다!
'프레임워크 > Spring' 카테고리의 다른 글
Spring의 @Retryable, @Recover 정리하기! (1) | 2024.10.20 |
---|---|
Jackson Deserializer 커스텀을 통해 LocalDateTime에 타임존 반영하기! (3) | 2024.03.16 |
[Spring] WireMock 기반의 테스트환경 구축하기 (0) | 2023.11.06 |
뜯어보며 배우는 DispatcherServlet의 @RequestMapping 기반 핸들러 매핑 과정 (2) | 2023.10.22 |
[Spring] 스프링 컨텍스트 로딩 시간을 포함한 전체 테스트 코드 수행시간 측정하기 (0) | 2023.05.18 |