들어가며
간단한 SimpleJob 예제의 TaskletStep의 실행 흐름을 디버깅을 통해 직접 확인해보자.
예제코드
TaskletStepArchitectureConfiguration.java
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.launch.support.RunIdIncrementer;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.support.ListItemReader;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
import java.util.List;
/*
--job.name=taskletArchitectureStepJob
*/
@Configuration
@RequiredArgsConstructor
public class TaskletStepArchitectureConfiguration {
// job 생성
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
@Bean
public Job taskletArchitectureStepJob() {
return this.jobBuilderFactory.get("taskletArchitectureStepJob")
.incrementer(new RunIdIncrementer())
.start(taskletArchitectureStepStep1())
.listener(new StepExecutionListener() {
// before
@Override
public void beforeStep(StepExecution stepExecution) {
}
// after
@Override
public ExitStatus afterStep(StepExecution stepExecution) {
return null;
}
})
.build();
}
@Bean
public Step taskletArchitectureStepStep1() {
return stepBuilderFactory.get("taskletArchitectureStepStep1")
.tasklet(new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
System.out.println("taskletArchitectureStepStep1");
return RepeatStatus.FINISHED;
}
})
.build();
}
}
TaskletStep 실행흐름
SimpleJob
SimpleJob 실행흐름은 아래 포스팅을 참고하자.
https://devfunny.tistory.com/932
디버깅을 통한 실행흐름 분석
1) SimpleJob.java > doExecute()
SimpleJob의 doExecute()에서 step을 반복적으로 수행
▶ Step1이 들어가있음을 확인할 수 있다.
2) SimpleStepHandler.java > handleStep()
▶ StepExecution을 생성한다.
▶ 생성한 StepExecution을 ExecutionContext에 셋팅한다.
3) AbstractStep.java > execute()
Listener가 있었다면 Step 수행 전에 수행하고, doExecute() 메서드를 통해 Step을 실행한다.
4) TransctionTemplate.java > execute()
5) TaskletStep.java > doExecute()
iterate()를 통해서 반복 수행된다. 콜백함수인, doIncChunkContext에서 트랜잭션 처리를 위한 로직을 수행한다.
6) StepContextRepeatCallback.java > doInIteration()
7) TransactionTemplate.java > execute()
8) TaskletStep.java > doInTransaction()
tasklet의 execute()를 통해서 수행시킨다. 여기서 우리가 TaskletStepArchitectureConfiguration에서 정의한 Tasklet 익명함수 로직이 수행된다.
▶ result가 null이면 RepeatStatus.FINISHED로 설정해주므로, null일때와 동일하다.
if (result == null) {
result = RepeatStatus.FINISHED;
}
▶ apply() 메서드를 호출하여 Step의 결과 카운팅을 셋팅한다.
apply()
9) AbstractStep.java > execute()
Step 실행 이후, ExitStatus, BatchStatus를 셋팅한다.
실행결과
taskletArchitectureStepStep1
RepeatStatus.CONTINUABLE로 바꿔보자
TaskletStepArchitectureConfiguration.java
@Bean
public Step taskletArchitectureStepStep1() {
return stepBuilderFactory.get("taskletArchitectureStepStep1")
.tasklet(new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
System.out.println("taskletArchitectureStepStep1");
return RepeatStatus.CONTINUABLE;
}
})
.build();
}
디버깅을 통한 실행흐름 분석 (위 분석의 8) 이후부터 다시 보자)
8) TaskletStep.java > doInTransaction()
Step 수행 이후, 마지막 부분에서 result가 "CONTINUABLE"이다.
9) TaskletStep.java > doExecute()
10) RepeatTemplate > executeInternal()
CONTINUABLE일때 while() 반복문이 계속 수행된다.
while(true) {
boolean var81 = false;
try {
...
for(i = 0; i < this.listeners.length; ++i) {
interceptor = this.listeners[i];
interceptor.before(context);
runnin=g = running && !this.isMarkedComplete(context);
}
if (running) {
try {
result = this.getNextResult(context, callback, state);
this.executeAfterInterceptors(context, result);
} catch (Throwable var82) {
this.doHandle(var82, context, deferred);
}
if (this.isComplete(context, result) || this.isMarkedComplete(context) || !deferred.isEmpty()) {
running = false;
}
}
...
11) RepeatTemplate.java > getNextResult()
12) StepContextRepeatCallback.java > doInIteration()
위 과정을 반복해서, 계속해서 배치를 수행하게된다.
null 또는 RepeatStatus.FINISHED를 리턴해야 수행을 한 후, 배치가 종료됨을 알 수 있다.
실행결과
taskletArchitectureStepStep1
taskletArchitectureStepStep1
taskletArchitectureStepStep1
..