BatchApplication.java에 @EnableBatchProcessing을 추가해서 Spring Batch 기능을 활성화하기
Job 패키지에 SimpleJobConfiguration 생성
@Slf4j // log 사용을 위한 lombok 어노테이션
@RequiredArgsConstructor // 생성자 DI를 위한 lombok 어노테이션
@Configuration //(1)
public class SimpleJobConfiguration {
//(2)
private final JobBuilderFactory jobBuilderFactory; // 생성자 DI 받음
private final StepBuilderFactory stepBuilderFactory; // 생성자 DI 받음
@Bean
public Job simpleJob() {
return jobBuilderFactory.get("simpleJob") //(3)
.start(simpleStep1())
.build();
}
@Bean
public Step simpleStep1() {
return stepBuilderFactory.get("simpleStep1") //(4)
.tasklet((contribution, chunkContext) -> { //(5)
log.info(">>>>> This is Step1");
return RepeatStatus.FINISHED;
})
.build();
}
}
(1) Spring Batch의 모든 Job은 @Configuration으로 등록해서 사용함.
(2) JobBuilderFactory, StepBuilderFactory의 DI를 받고 이걸 통해 Batch Job과 Batch Step을 생성함.
(3) "simpleJob"이라는 이름의 Batch Job을 생성하는 부분.
(4) "simpleStep1"이라는 Batch Step을 생성하는 부분.
(5) .tasklet()은 Step안에서 수행될 기능을 명시하는 부분. tasklet은 Step안에서 단일로 수행될 커스텀한 기능을 선언할 때 사용한다. 위 예제에서는 batch가 수행되면 log를 찍는다.
Simple Job안에서 start를 통해 simpleStep1을 품고 있는데, Job은 하나의 배치 작업 단위이고 이 안에 여러 Step이 존재한다. 그리고 그 안에는 Tasklet이나 Reader&processor&writer 묶음이 존재한다.
Step 안에 있다고 한 Tasklet 하나랑 Reader&Processor&Writer 묶음 하나가 같은 레벨이다.
그래서 Reader & Processor 가 끝나고 Tasklet 으로 마무리 짓는 식으로는 만들 수 없다.
Tasklet은 @Bean, @Component와 비슷하게, 명확한 역할은 없으나 개발자의 커스텀 기능을 위한 단위이다.
DBMS에서 실행해보기 with 메타데이터
Spring Batch의 메타 데이터가 가진 내용은,
이전에 실행한 Job이 뭐가 있는지
최근 실패한 Batch Parameter가 뭐가 있고, 성공한 Job은 뭐가 있는지
다시 실행한다면 어디서부터 시작하면 될지
어떤 Job에 어떤 Step이 있었고, Step들 중에서 성공한 건 뭐고 실패한 건 뭔지
등이 있고 이런 내용을 가진 테이블들이 있어야만 Spring Batch가 정상 동작한다. 그리고 이 부분은 Oracle이나 Mysql을 사용할 경우 개발자가 직접 생성해야만 한다.
우리 DB에도 있다!
메타 데이터 살펴보기
1. BATCH_JOB_INSTANCE
위 코드를 수행하면 simpleJob이 실행되면서 this is step1이 출력되는데, 그 Job이 DB에 기록되어 있다.
얘는 Job 파라미터에 따라 생성 되는 테이블인데 Job Parameter는 외부에서 받을 수 있는 파라미터다. 예를 들어 특정 날짜를 넘겨서 Job 안에서 작업할 수도 있고 출력할 수도 있다. 같은 Batch Job이더라도 Job Parameter가 다르면 이 테이블에 기록되고, Job Parameter가 다르면 기록되지 않는다.
위에서 실행한 simple job 코드를 변경해보자.
@Slf4j // log 사용을 위한 lombok 어노테이션
@RequiredArgsConstructor // 생성자 DI를 위한 lombok 어노테이션
@Configuration
public class SimpleJobConfiguration {
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
@Bean
public Job simpleJob() {
return jobBuilderFactory.get("simpleJob")
.start(simpleStep1(null))
.build();
}
@Bean
@JobScope //(1)
public Step simpleStep1(@Value("#{jobParameters[requestDate]}") String requestDate) { //(2)
return stepBuilderFactory.get("simpleStep1")
.tasklet((contribution, chunkContext) -> {
log.info(">>>>> This is Step1");
log.info(">>>>> requestDate = {}", requestDate); //(3)
return RepeatStatus.FINISHED;
})
.build();
}
}
(1) @JobScope를 빼먹으면 안된다. 이건 Step 선언문에서 사용 가능하고, Bean Scope를 지원한다. 스프링 빈의 기본 scope는 싱글톤인데, Spring Batch 컴포넌트인 Tasklet, ItemReader, ItemWriter, ItemProcessor 등에 @Bean 밑에 @JobScope를 해주면 저장된 Job의 실행시점에 해당 컴포넌트를 스프링 빈으로 등록한다. @StepScope도 Step 시작 시점에 스코프가 실행된다. 즉, Bean의 생성 시점을 지연시키는 것이다.
bean의 생성시점을 지연하는 것으로 인해 얻는 장점으로는 아래 두 가지가 있다.
Job Parameter를 Late Binding할 수 있음.
Job Parameter를 StepContext나 JobExecutionContext 레벨에서 할당시킬 수 있다. 꼭 어플리케이션이 실행되는 시점이 아니더라도 Controller나 Service와 같은 비즈니스 로직을 처리하는 부분에서 Job Parameter를 할당시킬 수 있는 것이다.
동일한 컴포넌트를 병렬 혹은 동시에 사용할 때 유용하다.
Step 안에 Tasklet이 있고 이 Tasklet은 멤버 변수를 갖고 있으면서 이 멤버변수를 변경하는 로직을 갖고 있을 때 @StepScope없이 Step을 병렬로 실행시킨다고 하자. 그러면 서로 다른 Step에서 하나의 Tasklet을 두고 마구잡이로 상태를 변경하려고 하게 될 것이다. 싱글톤으로 관리되는 step을 여러번 실행시키니까. / 그러나 stepScope을 사용한다면 각각의 Step에서 별도의 Tasklet을 생성하고 관리하기 때문에 서로의 상태를 침범하지 않는다.
(2) jobParameter를 설정하는 부분이다. edit configuration의 Program arguments를 통해 전달해서 실행하면 (3)의 내용이 로그에 찍히고, 기존과 다른 Job Parameter를 가졌기 때문에 테이블에 새로 기록된다.
이 상태에서 같은 파라미터로 다시 한번 Batch를 그대로 실행해보면 JobInstanceAlreadyCompleteException과 함께 job parameter를 변경하라고 한다. requestDate의 값을 변경하면 테이블에 잘 저장되고 배치도 실행된다.
즉, 동일한 Job Parameter는 여러 개 존재할 수 없다.정확히 말하면 동일한 Job Parameter로 '성공'한 기록이 있을 때만 재수행이 안된다. 실패 기록만 있다면 같은 job parameter로 해도 실행된다!
2. BATCH_JOB_EXECUTION
JOB_INSTANCE : JOB_EXECUTION = 부모 : 자식
부모인 JOB_INSTANCE가 성공/실패했던 내역을 모두 가진다.
그럼 실패시켜보자. Step을 그냥 정상적으로 FINISHED 시키기 전에 step 내에서 exception을 날리면 failed의 status와 함께 job이 실패하고 job_execution 테이블에 status와 함깨 기록된다.
3. BATCH_JOB_EXECUTION_PARAMS
위에서 넣었던 requestDate같은 파라미터의 값이 저장된다.
BATCH_JOB_FLOW
위에서 작성한 로그에 찍는 것과 같은 실제 작업은 Job이 아니라 Job안에 있는 Step에서 수행한다. 이처럼 Batch 처리할 내용을 담고 있는 게 Step이기 때문에 Job 내부에서 Step들 간 순서 혹은 처리 흐름을 제어할 필요가 있다. 여러 Step들을 어떻게 관리할까.
Next
@Slf4j
@Configuration
@RequiredArgsConstructor
public class StepNextJobConfiguration {
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
@Bean
public Job stepNextJob() {
return jobBuilderFactory.get("stepNextJob")
.start(step1())
.next(step2())
.next(step3())
.build();
}
@Bean
public Step step1() {
return stepBuilderFactory.get("step1")
.tasklet((contribution, chunkContext) -> {
log.info(">>>>> This is Step1");
return RepeatStatus.FINISHED;
})
.build();
}
@Bean
public Step step2() {
return stepBuilderFactory.get("step2")
.tasklet((contribution, chunkContext) -> {
log.info(">>>>> This is Step2");
return RepeatStatus.FINISHED;
})
.build();
}
@Bean
public Step step3() {
return stepBuilderFactory.get("step3")
.tasklet((contribution, chunkContext) -> {
log.info(">>>>> This is Step3");
return RepeatStatus.FINISHED;
})
.build();
}
}
next는 순차적으로 Step을 연결시킬 때 사용한다.
지정한 Batch Job만 실행되도록 하려면
application.yml 에 spring.batch.job.names: ${job.name:NONE}을 설정하면 된다. Program arguments로 job.name의 값이 들어오면 이 값과 일치하는 Job만 실행하는 코드이고, job.name이 있으면 이걸 할당하고, 없으면 NONE으로 할당한다는 의미이다. 그러나 NONE이 할당되면 어떤 배치도 실행하지 않겠다는 의미라서, 값이 없을 때 모든 배치가 실행되지 않도록 하는 역할이다.
조건별로 흐름 제어 (Flow)
상황에 따라 정상일 때는 Step B로, 오류가 났을 때는 Step C로 수행해야 할 때 사용한다.
@Bean
public Job stepNextConditionalJob() {
return jobBuilderFactory.get("stepNextConditionalJob")
.start(conditionalJobStep1())
.on("FAILED") // FAILED 일 경우
.to(conditionalJobStep3()) // step3으로 이동한다.
.on("*") // step3의 결과 관계 없이
.end() // step3으로 이동하면 Flow가 종료한다.
.from(conditionalJobStep1()) // step1로부터
.on("*") // FAILED 외에 모든 경우
.to(conditionalJobStep2()) // step2로 이동한다.
.next(conditionalJobStep3()) // step2가 정상 종료되면 step3으로 이동한다.
.on("*") // step3의 결과 관계 없이
.end() // step3으로 이동하면 Flow가 종료한다.
.end() // Job 종료
.build();
}
위처럼 job을 만드는 부분에서 실행할 step의 flow를 관리하고 있다.
on() : 캐치할 Exit Status를 설정, *일 경우 모든 경우를 말함.
얘가 캐치하는 건 Batch Status가 아니라 ExitStatus임!!
그러므로 분기 처리를 위한 상태값 조정이 필요하면 Exit Status를 조절해야 함.
예)
@Bean
public Step conditionalJobStep1() {
return stepBuilderFactory.get("step1")
.tasklet((contribution, chunkContext) -> {
log.info(">>> This is stepNextConditionalJob Step1");
contribution.setExitStatus(ExitStatus.FAILED);
return RepeatStatus.FINISHED;
})
.build();
}
to() : 다음으로 이동할 step 설정
from() : 이벤트리스너처럼, 상태값을 보고 일치하는 상태라면 to()에 설정된 step을 호출함. -> step1의 이벤트 캐치가 FAILED로 되어있는 상태에서 추가로 이벤트를 캐치하려면 from 을 써야만 한다.
end() : FlowBuilder를 반환하는 end와 FlowBuilder를 종료하는 end가 있다.
on("*") 뒤에 오는 end : FlowBuilder를 반환하는 end
build() 앞에 오는 end : FlowBuilder를 종료하는 end
Batch Status와 Exit Status의 차이
Batch Status는 Job 또는 Step의 실행 결과를 Spring에서 기록할 때 사용하는 Enum
COMPLETED, STARTING, STARTED, STOPPING, STOPPED, FAILED, ABANDEONED, UNKNOWN 이 있음.
하지만 본인이 직접 커스텀한, Batch Status와는 다른 exit Code가 필요하다면 아래와 같이 작성할 수 있다.
COMPLETED WITH SKIPS는 일단 step이 성공했는지를 확인한 다음 StepExecution의 skip 횟수가 0보다 클 경우에 일어나는 코드다.
//JobBuilderFactory.get() ~
.start(step1())
.on("FAILED")
.end()
.from(step1())
.on("COMPLETED WITH SKIPS") //커스텀이니까 얘의 코드를 반환하는 별도 로직 필요
.to(errorPrint1())
.end()
.from(step1())
.on("*")
.to(step2())
.end()
// SkepCheckingListener
public class SkipCheckingListener extends StepExecutionListenerSupport {
public ExitStatus afterStep(StepExecution stepExecution) {
String exitCode = stepExecution.getExitStatus().getExitCode();
if (!exitCode.equals(ExitStatus.FAILED.getExitCode()) &&
stepExecution.getSkipCount() > 0) {
return new ExitStatus("COMPLETED WITH SKIPS");
}
else {
return null;
}
}
}
Decide
위에서 진행한 Flow(분기), next(다음스텝순차적 정함)에는 문제가 있다.
Step이 담당하는 역할이 2개 이상이 된다. : 실제 Step이 처리해야 할 로직 외에도 분기 처리를 하기 위한 Exit Status 조정 필요
다양한 분기 로직 처리 어려움 : Exit Status를 커스텀하고 고치기 위해 Listener를 새로 생성하고 Job Flow에 등록해야 한다는 수고로움이 있음.
따라서, 명확하게 Step들 간의 Flow 분기만 담당하면서, 다양한 분기처리가 가능한 타입이 필요해서 생긴 게 Decide다.
아래 예제는 start Step -> oddDecider에서 홀짝 판별 -> oddStep 또는 evenStep 진행
이걸 구현한 decider를 flow 안에 넣는 방법은 아래와 같다.
@Bean
public Job deciderJob() {
return jobBuilderFactory.get("deciderJob")
.start(startStep())
.next(decider()) // startStep 이후에 decider()를 실행
.from(decider()) // decider의 상태가
.on("ODD") // ODD라면
.to(oddStep()) // oddStep로 간다.
.from(decider()) // decider의 상태가
.on("EVEN") // ODD라면
.to(evenStep()) // evenStep로 간다.
.end() // builder 종료
.build();
}
//decider() 구현체
@Bean
public JobExecutionDecider decider() {
return new OddDecider();
}
public static class OddDecider implements JobExecutionDecider {
@Override
public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {
Random rand = new Random();
int randomNumber = rand.nextInt(50) + 1;
log.info("랜덤숫자: {}", randomNumber);
if(randomNumber % 2 == 0) {
return new FlowExecutionStatus("EVEN");
} else {
return new FlowExecutionStatus("ODD");
}
}
}
Batch Scope와 Job Parameter
위에서 @JobScope, @StepScope에 대해 언급한 적이 있다.
Job Parameter 값을 변경하려 할 때 준 것 같은데, 어쨌든 Spring Batch에서는 외부 혹은 내부 파라미터를 받아 여러 Batch 컴포넌트에서 사용할 수 있게 지원한다. 이 파라미터를 Job Paramter라고 하는데, 이걸 사용하기 위해서는 항상 @JobScope, @StepScope 등 전용 Scope를 선언해야 한다.
@JobScope : Step 선언문에서 사용
@StepScope : Tasklet, ItemReader, ItemWriter, ItemProcessor에서 사용 가능.
현재 Job Parameter로 사용 가능한 타입은 Double, Long, Date, String이라서 LocalDate, LocalDateTime은 String으로 받아서 변환하여 사용해야 한다.
@StepScope, @JobScope 소개
Spring Batch 컴포넌트(Tasklet, ItemReader, ItemWriter, Processor 등)에 @StepScope를 사용하면 Spring Batch가 컨테이너를 통해 지정된 Step의 실행 시점에 해당 컴포넌트를 bean으로 등록한다. 그래서 bean의 생성 시점이 애플리케이션 시작 시점이 아닌 Scope가 시작되는 시점이 된다. 이렇게 실행 시점을 달리 했을 때의 좋은 점은 위에서 말했으니 넘어간다.
Job Parameter에 대해
Job Paramter는 @Value를 통해서 가능하고, Scope Bean(@JobScope나 @StepScope의 bean)을 생성해야만 가능하며, 생성하는 시점에 Job Parameter를 호출할 수 있다.
만약 일반적인 single tone bean으로 생성하게 되면 job Parameters cannot be found 에러가 발생한다. scope bean을 파라미터로 생성하든 클래스의 멤버변수로 생성하든 상관은 없으나 JobParamter를 사용하려면 반드시 @StepScope, @JobScope를 통해 scope bean을 생성해야 한다.
StepScope 사용 시 주의사항
reader()에서 ItemReader 타입을 리턴할 때 @StepScope 안에 proxyMode = ScopedProxyMode.TARGET_CLASS가 있기 때문에 ItemReader 인터페이스의 프록시 객체를 생성하여 리턴한다. @StepScope가 없으면 프록시가 아닌 생성한 객체 그대로를 bean으로 전달하는데, 이걸 쓰면 프록시 객체가 사용되는 것이다.
chunk 지향 처리
chunk : 스프링 배치에서 각 커밋에 처리되는 row 수로, 데이터 덩어리로 작업할 때 그 덩어리 단위.
chunk 지향 처리 : 한 번에 하나씩 데이터를 읽어서 chunk라는 덩어리를 만들고, 이 chunk단위로 트랜잭션을 다루는 것.
chunk만큼만 트랜잭션을 수행하니까 실패한다면 chunk만큼만 롤백이 되고, 이전 커밋된 트랜잭션 범위들은 그대로 반영한다.
READER
Spring Batch가 Chunk 지향 처리를 하고 이는 Job과 Step으로 구성되어 있다. Step은 Tasklet단위로 처리되고, Tasklet 중에서 ChunkOrientedTasklet을 통해 Chunk를 처리하며, 이걸 구성하는 요소에는 ItemReader, ItemWriter, ItemProcessor가 있다. ItemReader & ItemWriter & ItemProcessor의 묶음 도 Tasklet이다. ChunkOrientedTasklet에서 관리하니까
Reader로 읽을 수 있는 데이터
입력 데이터
파일로부터 읽기
DB로부터 읽기
Java Message Service등 다른 소스에서 읽기
본인이 커스텀한 Reader로 읽기
ItemReader의 구현체는 다양하게 있는데 대표적으로 JdbcPagingItemReader가 있고, ItemReader외에 ItemStream 인터페이스도 같이 구현하고 있다.
ItemReader는 read()만 가지고 있다.
ItemStream은 open(), update(), close()를 가지고 있고 주기적으로 상태를 저장, 오류 발생 시 해당 상태에서 복원하기 위한 마커 작업을 한다. 배치 프로세스의 실행 컨텍스트와 연계해서 ItemReader의 상태를 저장하고 실패한 곳에서 다시 실행할 수 있게 하는 역할이다.
open(), close() : 스트림을 열고 닫음
update() : Batch 처리의 상태를 업데이트할 수 있다.
개발자는 ItemReader, ItemStream을 직접 구현한 커스텀한 Reader를 만들 수 있다.
WRITER
Processor는 선택이다. Processor 없이도 chunkOrientedTasklet을 구성할 수 있다. 하지만 Reader, Writer는 필수다.
ItemWriter는 item을 하나씩 작성하지 않고 chunk 단위로 묶인 list를 다룬다.
Reader의 read()는 item을 하나 반환하는 반면, writer의 write()은 ItemList 를 인자로 받는다.
즉, ItemReader를 통해 각 항목을 개별적으로 읽고, 이걸 처리하기 위해 ItemProcessor에 전달한 뒤, 청크만큼의 처리가 완료되면 이 청크만큼의 list를 writer에 전달해서 write한다.
PROCESSOR
ItemProcessor는 데이터를 가공하거나 필터링하는 역할을 한다.
이건 필수가 아니고, writer에서도 충분히 구현이 가능하다. 그런데도 processor를 사용하는 이유는, 비즈니스 코드가 섞이는 것을 방지하기 위해서이다.
ItemProcessor를 사용하는 방법
변환 : Reader에서 읽은 데이터를 원하는 타입으로 변환해서 writer에 넘겨주기
필터 : Reader에서 넘겨준 데이터를 writer로 넘겨줄지 말지를 결정하는 필터 구현 가능(null을 반환하면 writer에 전달되지 않는다.)
ItemProcessor<I, O> : I는 reader에서 받을 데이터 타입이고 O는 writer로 보낼 데이터 타입을 명시하면 된다.
Batch 목록
Job 생성하기
BatchApplication.java에 @EnableBatchProcessing을 추가해서 Spring Batch 기능을 활성화하기
Job 패키지에 SimpleJobConfiguration 생성
(1) Spring Batch의 모든 Job은 @Configuration으로 등록해서 사용함. (2) JobBuilderFactory, StepBuilderFactory의 DI를 받고 이걸 통해 Batch Job과 Batch Step을 생성함. (3) "simpleJob"이라는 이름의 Batch Job을 생성하는 부분. (4) "simpleStep1"이라는 Batch Step을 생성하는 부분. (5) .tasklet()은 Step안에서 수행될 기능을 명시하는 부분. tasklet은 Step안에서 단일로 수행될 커스텀한 기능을 선언할 때 사용한다. 위 예제에서는 batch가 수행되면 log를 찍는다. Simple Job안에서 start를 통해 simpleStep1을 품고 있는데, Job은 하나의 배치 작업 단위이고 이 안에 여러 Step이 존재한다. 그리고 그 안에는 Tasklet이나 Reader&processor&writer 묶음이 존재한다.
Step 안에 있다고 한 Tasklet 하나랑 Reader&Processor&Writer 묶음 하나가 같은 레벨이다. 그래서 Reader & Processor 가 끝나고 Tasklet 으로 마무리 짓는 식으로는 만들 수 없다.
DBMS에서 실행해보기 with 메타데이터
Spring Batch의 메타 데이터가 가진 내용은,
메타 데이터 살펴보기
1. BATCH_JOB_INSTANCE
위 코드를 수행하면 simpleJob이 실행되면서 this is step1이 출력되는데, 그 Job이 DB에 기록되어 있다. 얘는 Job 파라미터에 따라 생성 되는 테이블인데 Job Parameter는 외부에서 받을 수 있는 파라미터다. 예를 들어 특정 날짜를 넘겨서 Job 안에서 작업할 수도 있고 출력할 수도 있다. 같은 Batch Job이더라도 Job Parameter가 다르면 이 테이블에 기록되고, Job Parameter가 다르면 기록되지 않는다. 위에서 실행한 simple job 코드를 변경해보자.
(1) @JobScope를 빼먹으면 안된다. 이건 Step 선언문에서 사용 가능하고, Bean Scope를 지원한다. 스프링 빈의 기본 scope는 싱글톤인데, Spring Batch 컴포넌트인 Tasklet, ItemReader, ItemWriter, ItemProcessor 등에 @Bean 밑에 @JobScope를 해주면 저장된 Job의 실행시점에 해당 컴포넌트를 스프링 빈으로 등록한다. @StepScope도 Step 시작 시점에 스코프가 실행된다. 즉, Bean의 생성 시점을 지연시키는 것이다.
bean의 생성시점을 지연하는 것으로 인해 얻는 장점으로는 아래 두 가지가 있다.
(2) jobParameter를 설정하는 부분이다. edit configuration의 Program arguments를 통해 전달해서 실행하면 (3)의 내용이 로그에 찍히고, 기존과 다른 Job Parameter를 가졌기 때문에 테이블에 새로 기록된다.
이 상태에서 같은 파라미터로 다시 한번 Batch를 그대로 실행해보면 JobInstanceAlreadyCompleteException과 함께 job parameter를 변경하라고 한다. requestDate의 값을 변경하면 테이블에 잘 저장되고 배치도 실행된다. 즉, 동일한 Job Parameter는 여러 개 존재할 수 없다. 정확히 말하면 동일한 Job Parameter로 '성공'한 기록이 있을 때만 재수행이 안된다. 실패 기록만 있다면 같은 job parameter로 해도 실행된다!
2. BATCH_JOB_EXECUTION
3. BATCH_JOB_EXECUTION_PARAMS
BATCH_JOB_FLOW
위에서 작성한 로그에 찍는 것과 같은 실제 작업은 Job이 아니라 Job안에 있는 Step에서 수행한다. 이처럼 Batch 처리할 내용을 담고 있는 게 Step이기 때문에 Job 내부에서 Step들 간 순서 혹은 처리 흐름을 제어할 필요가 있다. 여러 Step들을 어떻게 관리할까.
Next
지정한 Batch Job만 실행되도록 하려면
application.yml 에 spring.batch.job.names: ${job.name:NONE}을 설정하면 된다. Program arguments로 job.name의 값이 들어오면 이 값과 일치하는 Job만 실행하는 코드이고, job.name이 있으면 이걸 할당하고, 없으면 NONE으로 할당한다는 의미이다. 그러나 NONE이 할당되면 어떤 배치도 실행하지 않겠다는 의미라서, 값이 없을 때 모든 배치가 실행되지 않도록 하는 역할이다.
조건별로 흐름 제어 (Flow)
위처럼 job을 만드는 부분에서 실행할 step의 flow를 관리하고 있다.
Batch Status와 Exit Status의 차이
Exit Status는 Step의 실행 후 상태이고, Enum이 아니다.
public ExitStatus afterStep(StepExecution stepExecution) { String exitCode = stepExecution.getExitStatus().getExitCode(); if (!exitCode.equals(ExitStatus.FAILED.getExitCode()) && stepExecution.getSkipCount() > 0) { return new ExitStatus("COMPLETED WITH SKIPS"); } else { return null; } } }
Decide
위에서 진행한 Flow(분기), next(다음스텝순차적 정함)에는 문제가 있다.
아래 예제는 start Step -> oddDecider에서 홀짝 판별 -> oddStep 또는 evenStep 진행 이걸 구현한 decider를 flow 안에 넣는 방법은 아래와 같다.
Batch Scope와 Job Parameter
위에서 @JobScope, @StepScope에 대해 언급한 적이 있다. Job Parameter 값을 변경하려 할 때 준 것 같은데, 어쨌든 Spring Batch에서는 외부 혹은 내부 파라미터를 받아 여러 Batch 컴포넌트에서 사용할 수 있게 지원한다. 이 파라미터를 Job Paramter라고 하는데, 이걸 사용하기 위해서는 항상 @JobScope, @StepScope 등 전용 Scope를 선언해야 한다.
@StepScope, @JobScope 소개
Spring Batch 컴포넌트(Tasklet, ItemReader, ItemWriter, Processor 등)에 @StepScope를 사용하면 Spring Batch가 컨테이너를 통해 지정된 Step의 실행 시점에 해당 컴포넌트를 bean으로 등록한다. 그래서 bean의 생성 시점이 애플리케이션 시작 시점이 아닌 Scope가 시작되는 시점이 된다. 이렇게 실행 시점을 달리 했을 때의 좋은 점은 위에서 말했으니 넘어간다.
Job Parameter에 대해
Job Paramter는 @Value를 통해서 가능하고, Scope Bean(@JobScope나 @StepScope의 bean)을 생성해야만 가능하며, 생성하는 시점에 Job Parameter를 호출할 수 있다. 만약 일반적인 single tone bean으로 생성하게 되면 job Parameters cannot be found 에러가 발생한다. scope bean을 파라미터로 생성하든 클래스의 멤버변수로 생성하든 상관은 없으나 JobParamter를 사용하려면 반드시 @StepScope, @JobScope를 통해 scope bean을 생성해야 한다.
StepScope 사용 시 주의사항
reader()에서 ItemReader 타입을 리턴할 때 @StepScope 안에 proxyMode = ScopedProxyMode.TARGET_CLASS가 있기 때문에 ItemReader 인터페이스의 프록시 객체를 생성하여 리턴한다. @StepScope가 없으면 프록시가 아닌 생성한 객체 그대로를 bean으로 전달하는데, 이걸 쓰면 프록시 객체가 사용되는 것이다.
chunk 지향 처리
READER
Spring Batch가 Chunk 지향 처리를 하고 이는 Job과 Step으로 구성되어 있다. Step은 Tasklet단위로 처리되고, Tasklet 중에서 ChunkOrientedTasklet을 통해 Chunk를 처리하며, 이걸 구성하는 요소에는 ItemReader, ItemWriter, ItemProcessor가 있다. ItemReader & ItemWriter & ItemProcessor의 묶음 도 Tasklet이다. ChunkOrientedTasklet에서 관리하니까
Reader로 읽을 수 있는 데이터
WRITER
Processor는 선택이다. Processor 없이도 chunkOrientedTasklet을 구성할 수 있다. 하지만 Reader, Writer는 필수다.
PROCESSOR
ItemProcessor는 데이터를 가공하거나 필터링하는 역할을 한다. 이건 필수가 아니고, writer에서도 충분히 구현이 가능하다. 그런데도 processor를 사용하는 이유는, 비즈니스 코드가 섞이는 것을 방지하기 위해서이다.
ItemProcessor를 사용하는 방법