스프링 배치에서 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();
//    }

 

정상적으로 실행하는 것 역시 확인할 수 있다.

 

 

해당 프로젝트 코드는 깃허브에서 확인 가능합니다.

 

GitHub - brick0123/spring-boot-in-action: 📚 spring pratice repository

📚 spring pratice repository. Contribute to brick0123/spring-boot-in-action development by creating an account on GitHub.

github.com