Post

(Spring Batch) cli 실행 - JobLauncherApplicationRunner

SpringBoot 안쓰는 경우 - CommandLineJobRunner

SpringBoot 쓰는 경우 - yml 이용한 세팅

즉, SpringBoot3에서는 JobLauncherApplicationRunner를 사용하면 된다.

BatchAutoConfiguration에서 JobLauncherApplicationRunner bean을 만들어준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@ConditionalOnMissingBean(value = DefaultBatchConfiguration.class, annotation = EnableBatchProcessing.class)
public class BatchAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = "spring.batch.job", name = "enabled", havingValue = "true", matchIfMissing = true)
    public JobLauncherApplicationRunner jobLauncherApplicationRunner(JobLauncher jobLauncher, JobExplorer jobExplorer,
            JobRepository jobRepository, BatchProperties properties) {
        JobLauncherApplicationRunner runner = new JobLauncherApplicationRunner(jobLauncher, jobExplorer, jobRepository);
        String jobNames = properties.getJob().getName();
        if (StringUtils.hasText(jobNames)) {
            runner.setJobName(jobNames);
        }
        return runner;
    }

SpringBoot 3 부터는 spring.batch.job.enabled=true 일 때, jobName 누락하더라도 전체 job이 실행되지 않는다.

SpringBoot 2 까지는 spring.batch.job.enabled=false로 만들어야 jobName을 누락하는 경우 전체 job 실행되지 않았던 것 같은데,
SpringBoot 3 부터는 spring.batch.job.enabled=true여도 jobName 누락하더라도 전체 job이 실행되지 않는다. 옵션을 true로 놓아도 무방하다.

1
2
Caused by: java.lang.IllegalArgumentException: Job name must be specified in case of multiple jobs
    at org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner.validate

JobLauncherApplicationRunner의 문제점? - 알 수 없는 job name을 넘겨도 배치가 실패하지 않는다.

따라서 별도 PostConstructor를 이용해 검사하거나, 아예 JobLauncherApplicationRunner 빈 자체를 직접 생성하면서 검사하는 것이 좋아보인다. 요구사항을 만족하는 빈을 직접 생성하기로 했다.

JobLauncherApplicationRunner 생성 빈 직접 정의하기

요구사항 정리

  • JobParameter 목록에 현재시각을 자동으로 추가해주어야 한다.
    • 생각해보니 이 요구사항은 제거해도 될 것 같다.
    • 배치에서 사용할 기준 시간이 필요해서 받는거라면, 이를 꼭 JobParameter로 받을 필요는 없다.
      • 하지만 static 변수나 싱글턴 변수 보다는 param으로 받는게 테스트 하기에는 더 낫지 않나 싶다.
    • 같은 파라미터로 재수행 불가하기 때문에 파라미터 바꿔주기 위해 받는거라면, 이는 allowStartIfComplete 설정이나, Increment를 넣어주면 된다.
  • cli에서 key1=value1 key2=value2 형식으로 잡파라미터 받을 수 있어야 한다.
  • 알 수 없는 job name이 들어왔을 때, 실패해야 한다. (ExitCode도 비정상 반환)
  • 배치 중간에 실패했을 때, 애플리케이션 ExitCode를 비정상으로 반환해야 한다.

직접 JobLauncherApplicationRunner Bean 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@ConditionalOnMissingBean(value = DefaultBatchConfiguration.class, annotation = EnableBatchProcessing.class)  // BatchAutoConfiguration 없이 단독으로 JobLauncherApplicationRunner bean만 생성되는 경우를 방지하기 위해 방어로직 추가
@EnableConfigurationProperties(BatchProperties::class)
@Configuration
class JobLauncherApplicationRunnerConfig {
    @Bean
    fun jobLauncherApplicationRunner(
        jobLauncher: JobLauncher, jobExplorer: JobExplorer, jobRepository: JobRepository,
        properties: BatchProperties, jobs: Collection<Job>
    ): JobLauncherApplicationRunner {
        val runner = JobLauncherApplicationRunner(jobLauncher, jobExplorer, jobRepository)
        val jobName = properties.job.name
        if (!jobs.map { it.name }.contains(jobName)) {
            logger.error { "### job Bean [$jobName] 찾을 수 없음" }
            throw IllegalArgumentException("### job Bean [$jobName] 찾을 수 없음")
        }
        runner.setJobName(jobName)
        return runner
    }
}

DefaultBatchConfiguration가 존재하면 BatchAutoConfig가 적용되지 않고
BatchAutoConfig가 적용되지 않으면 JobExecutionExitCodeGenerator를 비롯한 여러 빈들이 생성되지 않는다.
i.e. ExitCodeGenerator 빈이 없으면 exit code가 정상적으로 세팅되지 않는 등 JobLauncherApplicationRunner가 예상한대로 동작하지 않으므로
ConditionalOnMissingBean으로 BatchAutoConfiguration이 적용 되었는지 방어로직 꼭 넣어주는 것이 좋다.

1
2
3
4
5
6
7
8
9
10
11
@SpringBootApplication
class MyBatchApplication
fun main(args: Array<String>) {
    try {
        val applicationContext = runApplication<ApprovalBatchApplication>(*args)
        val exitCode = SpringApplication.exit(applicationContext)
        exitProcess(exitCode)
    } catch (e: Exception) {
        exitProcess(101)  // java.lang.IllegalStateException: Failed to execute ApplicationRunner 케이스
    }
}
1
2
3
4
5
6
spring:
    jdbc:
        isolation-level-for-create: DEFAULT
    job:
        enabled: true
        name: \${job.name:NONE}
1
$ batch.jar --job.name=실행할JobName arg1=value1 arg2=value2

위와 같이 세팅하면 요구사항을 만족 할 수 있다.

contribution

이런 기능은 아예 JobLauncherApplicationRunner에서 제공해주는게 좋아보여 이슈를 올렸는데.

https://github.com/spring-projects/spring-boot/issues/35940

갑자기 나타난 인도인이 PR을 먼저 올리면서 그 친구가 담당해서 처리하게 되었다…;; (PR을 보니 contribution이 무지 하고싶었나봄… Author에 자기 이름도 넣어두고. 그래그래 이해한다 ㅎ)

아무튼 잘 처리해 주시길…

Test시에는 어떻게?

https://www.baeldung.com/spring-junit-prevent-runner-beans-testing-execution

참고) executeLocalJobs와 executeRegisteredJobs의 차이?

1
2
3
4
5
6
7
// local jobs는 JobLauncherApplicationRunner 빈 생성 시점에 DI 받아 확정
@Autowired(required = false)
public void setJobs(Collection<Job> jobs) {
    this.jobs = jobs;
}

// 반면 registered jobs는 런타임에 등록된 job들에 대한 컨테이너. (JobRegistry 통해 런타임에 잡 등록 가능)
This post is licensed under CC BY 4.0 by the author.