스프링 배치에서 JobParameters을 더 편하게 사용하는 방법
스프링 배치에서 JobPameter를 사용할때 외부에서 주입받아서 사용하는 경우가 많다.
대표적으로 "날짜"가 있다. 하지만 SpringBatch에서는 LocalDateTime을 지원하지 않아서 다음과 같이 타입 변환 작업이 이루어져야한다.
@Bean
@StepScope
public ItemReader<Long> simpleReader(
@Value("#{jobParameters[requestDate]}") String requestDate
) {
LocalDate date = LocalDate.parse(requestDate, DateTimeFormatter.ISO_DATE); // yyyy-MM-dd
log.info(">>> date = {}", date);
return new ListItemReader<>(List.of(1L, 2L, 3L));
}
이런 방식에는 다음과 같은 단점들이 있다.
- LocalDate로 바꾸는 코드를 중복해서 작성하게 된다.
- processor, writer에서 해당 requestDate가 필요할 때 건내주거나 받기 까다롭다.
- 파라미터명을 requestDate라고 이미 선언했기 때문에 지역 변수 이름은 다른걸 사용해야한다.
- simpleReader(null) 이런식으로 step측에서 null을 인자로 주어야하는데, 모르는 사람이 보았을때는 NPE가 발생하거나 등의 문제로 인식할 수 있다.
대안
이제 좀 더 편안하게 사용할 수 있는 방법을 알아보자. 바로 requestParameter 갖고 있는 별도의 클래스를 선언하고 빈으로 등록해서 사용하는 것이다.
@Getter
public class RequestDateJobParameter {
private LocalDate requestDate;
@Value("#{jobParameters[requestDate]}")
public void setRequestDate(String requestDate) {
if (StringUtils.hasText(requestDate)) { // (1)
this.requestDate = LocalDate.parse(requestDate, DateTimeFormatter.ISO_DATE);
}
}
}
@Slf4j
@Configuration
@RequiredArgsConstructor
public class SimpleChunk {
private static final String JOB_NAME = "simpleChunkJob";
private static final String BEAN_PREFIX = JOB_NAME + "_";
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
private final RequestDateJobParameter requestDateJobParameter;
@Bean(BEAN_PREFIX + "jobParameter")
@JobScope
public RequestDateJobParameter jobParameter() {
return new RequestDateJobParameter();
}
...
@Bean(BEAN_PREFIX + "reader")
@StepScope
public ItemReader<Long> simpleReader() {
final LocalDate requestDate = requestDateJobParameter.getRequestDate();
log.info(">>> requestDate = {}", requestDate);
return new ListItemReader<>(List.of(1L, 2L, 3L));
}
..
}
(1): null 체크를 무조건 해줘야한다. 빈으로 등록했기 때문에 해당 jobParameter(여기서는 requestDate)가 필요 없는 job을 실행할 때는 당연히 인자값으로 전달하지 않기 때문에 NPE가 발생한다. (spring.batch.job.names: ${JOB_NAME:NONE} 해당 옵션은 job실행이랑 관련이 있는것이지, 빈 등록이랑은 관련이 없다)
위와 같은 방식으로 값이 정상적으로 잘 동작하는 것을 확인할 수 있다. 필드에 선언되어있기 때문에 이제 processor에서도 requestDate가 필요한 경우 손쉽게 사용할 수 있다. (물론 @StepScope를 선언해야한다)
이제 위 방식으로 배치 job들에서 사용하는 requestDate를 변경하면 다음과 같은 오류가 발생할 것이다.
(좀 더 정확하게 얘기하면 2개 이상의 배치 Job에서 RequestDateJobParameter를 위와 같은 방식으로 사용할 경우)
로그를 잘 살펴보자.
간략하게 얘기하자면 MultiThreadJobConfig에서도 RequestDateJobParameter 의존 관계를 필요로 하고 있다. 하지만 두 곳 이상의 배치 job에서 RequestDateJobParameter 빈을 띄웠으면, 타입이 동일한 빈 여러개가 스프링 컨테이너에 등록되어 있을 것이다. 그래서 어떤 녀석을 주입 받을지 몰라서 오류가 발생한 것이다.
(타입은 동일하더라도 빈 이름이 다르기 때문에 등록 자체는 문제가 없다)
이를 해결하기 위해서는 각자의 job의 @Qualifier를 선언해서 해당 job에서 선언한 파라미터 이름을 명시해서 주입받으면 해결된다. 하지만 이는 좋은 방법이 아니다. job이 n개면 n개만큼의 빈이 등록되게 된다.
해결 방법
따라서 다음과 같이 한 곳에서 빈을 등록하고 관리해주면 된다.
@Configuration(proxyBeanMethods = false)
public class JobParameterConfig {
@Bean
@JobScope
public RequestDateJobParameter requestDateParam() {
return new RequestDateJobParameter();
}
}
이제 각 Job에서 다음과 같이 파라미터 빈을 설정한 것을 모두 지워버리면 된다.
// Job 내부에 RequestDateJobParameter를 선언한것들은 다 지워버린다
// @Bean(BEAN_PREFIX + "parameter")
// @JobScope
// public RequestDateJobParameter requestDateParam() {
// return new RequestDateJobParameter();
// }
정상적으로 실행하는 것 역시 확인할 수 있다.
해당 프로젝트 코드는 깃허브에서 확인 가능합니다.
'Spring Data & Batch' 카테고리의 다른 글
Spring Batch 통합 테스트 환경 구축하기 (0) | 2022.08.03 |
---|---|
스프링과 EntityManager의 동시성 비밀 (0) | 2021.11.20 |
Optimistic Lock으로 선착순 한 명 쿠폰을 만들어보자 (0) | 2021.08.28 |
[Spring Batch] 하나의 Step에서 여러개의 ItemWriter를 사용하는 방법 (0) | 2021.08.04 |
One-to-One 관계에서 Lazy 로딩은 언제 동작되는 것일까? (0) | 2021.06.17 |