Spring boot 3
기반의 Spring batch 5
프로젝트를 생성spring batch
를 위한 설정법 알아보기Job
의 수동실행 및 테스트 코드 작성법필자 개발환경:
- IDE:
IntelliJ IDEA 2024.2.1 (Ultimate Edition)
- OS :
Ubuntu 22.04 (via WSL2)
- DB :
PostgreSQL 16 (via Docker)
- JDK :
temurin 21
- Build Tool :
Maven
- 그외:
Spring Boot 3.3.3
,Spring Batch Core 5.1.2
Spring boot 3 기반의 프로젝트를 생성해봅시다.
https://start.spring.io/ 에 접속해서 아래와 같은 dependencies 를 갖는
프로젝트를 하나 생성하고, 자신이 사용하는 IDE 를 통해서 여시기 바랍니다.
이후 spring boot 기본 프로젝트를 열면 src/main/resources/
경로에 있는
application.properties
파일을 아래와 같이 편집합니다.
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.datasource.driver-class-name=org.postgresql.Driver
spring.batch.jdbc.initialize-schema=always
spring.batch.jdbc.platform=postgresql
spring.batch.job.enabled=false
yaml 사용시에는 아래처럼
spring: datasource: url: jdbc:postgresql://localhost:5432/postgres username: postgres password: postgres batch: jdbc: initialize-schema: always platform: postgresql job: enabled: false
각 설정의 의미
(spring datasource 부분은 여러분 환경에 맞게 수정하세요!)
spring.batch.jdbc.initialize-schema=always
:
always
로 하면 처음 한번 생성, 이후 기존 생성 테이블을 계속 사용never
를 지정하면, 미리 자신이 해당 스키마 테이블들을 수동으로 생성해놔야 합니다.spring-batch-core
jar 내부에 있습니다.spring.batch.jdbc.platform=postgresql
:
위에서 말한 initialize-schema
설정에 의해서 메타 정보 테이블을 생성할 때
사용하게 될 스크립트를 지정하는 옵션입니다. 옵션값은 schema-<옵션값>.sql
처럼 사용되어서 실제 테이블 생성 SQL 파일을 지정하게 됩니다. sql 파일들은 모두 spring-batch-core
jar 내부에 있습니다.
spring.batch.job.enabled=false
: Job
이 Bean 으로 등록되어 있으면 spring boot
실행시 자동으로Job
도 실행합니다. 이를 방지하는 설정입니다.spring batch 4.x 버전을 사용하시던 분들이라면
Execution Context 의 정보가 json 형태로 저장되던 것을 기억할 겁니다.
하지만 5 버전에서는 Base64 형태로 컨텍스트 정보가 저장이 되는데요.
만약에 이전처럼 json 으로 저장하고 싶다면 아래처럼 설정해주세요.
주의: 아래와 같이 jackson 라이브러리를 미리 세팅해야 됩니다.
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> </dependency>
@SpringBootApplication
public class SpringBatchPlaygroundApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBatchPlaygroundApplication.class, args);
}
// **** 아래처럼 빈 하나를 등록하면 끝입니다. ****
@Bean
public ExecutionContextSerializer executionContextSerializer() {
return new Jackson2ExecutionContextStringSerializer();
}
}
목차는 spring boot 3.3.x 이하 버전을 사용할 때 나오는 WARNING 로그를
없애기 위한 설정입니다. spring boot 3.4.x 이상의 버전을 사용하시는
분들은 이 목차를 읽지말고 넘어가시면 됩니다.
Spring batch 에서 사용되는 JobRegistry 을 위해 제공되는
Spring boot 3 의 자동설정에는 현재 약간 문제가 있습니다.
이를 해결하기 위해서는 다음과 같이 2개의 Bean 을 등록하면 됩니다.
BeanDefinitionRegistryPostProcessor
:JobRegistryBeanPostProcessor
제거JobRegistrySmartInitializingSingleton
:아래 코드처럼 작성하면 됩니다.
package coding.toast.batch;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class SpringBatchStudyApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBatchStudyApplication.class, args);
}
// 1. jobRegistryBeanPostProcessor 비활성화
@Bean
public static BeanDefinitionRegistryPostProcessor jobRegistryBeanPostProcessorRemover() {
return registry -> registry.removeBeanDefinition("jobRegistryBeanPostProcessor");
}
// 2. jobRegistryBeanPostProcessor 를 대체할 JobRegistrySmartInitializingSingleton 빈 등록
@Bean
public JobRegistrySmartInitializingSingleton
jobRegistrySmartInitializingSingleton(JobRegistry jobRegistry) {
return new JobRegistrySmartInitializingSingleton(jobRegistry);
}
}
여기서부터 조금 설명이 길어지는데, 그냥 프로젝트 설정만 빠르게 하실분들은
다음 목차로 바로 이동하시면 됩니다. 그냥 에러가 왜나는지에 대한 설명입니다.
먼저 jobRegistryBeanPostProcessor
를 제거하지 안 하고
프로젝트를 실행하면 아래와 같이 많은 WARN
문구가 나옵니다.
이는 BPP(BeanPostProcessor)
와 일반 Bean
의 등록 순서 문제입니다.
(이 문제가 뭔지 궁금하면 이 링크를 참고하세요)
이와 관련해서는 깃헙 이슈에도 문의 글이 한번 올라왔었죠.
그리고 해당 글에서는 이에 대한 해결법으로 문제를 일으키는 BPP 를 제거하라고 합니다.
@Bean
public static BeanDefinitionRegistryPostProcessor jobRegistryBeanPostProcessorRemover() {
return registry ->
registry.removeBeanDefinition("jobRegistryBeanPostProcessor");
}
그런데 어떤 분들은 이렇게 하니 기존에 JobRegistry
기능을 사용하는 부분에서
에러가 난다고 하더군요. 관련 설정을 지웠으니 어찌보면 당연합니다.
그러면 WARN 이 보여도 억지로 jobRegistryBeanPostProcessor
를 써야할까요?
다행히 JobRegistry 의 설정 방법에는 jobRegistryBeanPostProcessor
외에도
JobRegistrySmartInitializingSingleton
를 통해서도 가능합니다.
그래서 이 부분 또한 코드에 추가했던 거죠.
@Bean
public JobRegistrySmartInitializingSingleton
jobRegistrySmartInitializingSingleton(JobRegistry jobRegistry) {
return new JobRegistrySmartInitializingSingleton(jobRegistry);
}
이제 제가 위에서 말한 2개의 Bean 을 등록한 이유가 이해가 되죠?
이렇게 설정하면 WARN 로그가 나오지 않고, JobRegistry
도 평상시처럼
사용할 수 있게 됩니다.
참고로 이 부분은
spring batch 5.2
부터 고쳐질 것이라고 합니다.
하지만 현재 기준spring boot release
버전인3.3.3
에서는
spring batch core
버전이5.1.2
이므로 안탑깝지만 위처럼 수동으로 문제를 해결해야 합니다.
Spring boot 3.4.x 부터 batch core 5.2.x 를 사용될 예정입니다.
https://github.com/spring-projects/spring-boot/releases/tag/v3.4.0-M3
잘 설정됐는지 확인하기 위해서 Spring Boot
프로젝트를 실행해봅시다.
위처럼 로그에 WARN
로그가 없으면 일단 1차적으로 OK 입니다.
2번째로는 DB
에 Spring Batch
가 메타정보를 저장하는 용도의 테이블들과
사용되는 시퀀스들이 생성된 것이 확인되면 성공한 겁니다.
프로젝트 정상 구동은 확인으니, 이제 간단한 Job, Step 을 하나 생성하고
곧바로 구동을 시켜보겠습니다.
먼저 설정 클래스 하나 생성하고, 안에 Job, Step Bean 을 하나씩 생성해줍니다.
package coding.toast.batch.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
@Slf4j
@Configuration
public class MyJobConfiguration {
@Bean("job-first")
public Job job1(JobRepository jobRepository,
PlatformTransactionManager platformTransactionManager) {
return new JobBuilder("job-first", jobRepository)
.start(step1(jobRepository, platformTransactionManager))
.build();
}
@Bean("step-first")
public Step step1(JobRepository jobRepository,
PlatformTransactionManager platformTransactionManager) {
return new StepBuilder("step-first", jobRepository)
.tasklet((contribution, chunkContext) -> {
log.info("step1 executed");
return RepeatStatus.FINISHED;
}, platformTransactionManager)
.build();
}
}
해당 Job 을 수동으로 실행시키는 법은 간단합니다.
JobLauncher 와 실행시키고자 하는 Job 을 DI 받고 사용하면 끝입니다.
아래 코드 처럼 말이죠.
package coding.toast.batch;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.configuration.JobRegistry;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
// ... 나머지 import 생략 ...
@SpringBootApplication
public class SpringBatchStudyApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBatchStudyApplication.class, args);
}
// ... 중간 생략 ...
// 간단한 테스트를 위해 프로젝트 실행할 때 자동으로 Job Launcher 실행시키기
@Bean
public CommandLineRunner commandLineRunner(JobLauncher jobLauncher,
@Qualifier("job-first") Job job) {
return args -> {
JobParameters jobParameters = new JobParametersBuilder().toJobParameters();
jobLauncher.run(job, jobParameters);
};
}
}
주의사항
이렇게 하면 Job 을 바로 실행할 수 있습니다.
다만 여기서 알아야 할 점은 JobParameter
의 세팅을 바꾸지 않는 이상
한번 실행 성공한 Job 은 재실행되지 않는다는 점입니다.
개발시에는 여러번 Job 을 실행해서 테스트를 해야 되는 경우가 많은데,
이러한 동작 방식은 약간 걸리적 거리죠.
이런 경우에는 spring-batch-test
에서 제공하는 API 사용하는 게 좋습니다.
그 방법은 바로 다음 목차에서 보겠습니다.
프로젝트가 계속 발전하다 보면 여러개의 Job 이 생성되고,
이를 여러번 돌려보면서 테스트를 해보고 싶은 경우가 오는데요.
이때 사용하면 편한게 spring-batch-test 의 JobLauncherTestUtils
입니다.
package coding.toast.batch;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.batch.core.*;
import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.batch.test.context.SpringBatchTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
@SpringBatchTest
public class SimpleLogBatchTest {
@Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
@BeforeEach
public void setup(@Qualifier("job-first") Job job1) {
this.jobLauncherTestUtils.setJob(job1);
}
@Test
public void testMyJob() throws Exception {
// given
// 무조건 적으로 Job 을 재실행하도록 랜덤함 JobParameter 값 하나를 추가한 JobParamBuilder 생성
JobParametersBuilder uniqueJobParametersBuilder = this.jobLauncherTestUtils.getUniqueJobParametersBuilder();
// 필요하다면 여기서 parameter builder 에 필요한 파라미터 정보 추가
JobParameters jobParameters = uniqueJobParametersBuilder.toJobParameters();
// when
JobExecution jobExecution = this.jobLauncherTestUtils.launchJob(jobParameters);
// then
Assertions.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus());
}
}
getUniqueJobParametersBuilder
를 통해서 매번 유니크한 Long 타입의프로젝트 구현 시, batch 전용 db 스키마를 생성하고 사용하고 싶을 수 있습니다.
그럴 때는 아래처럼 하면 됩니다.
먼저 application.properties 를 아래처럼 수정합니다.
spring.batch.jdbc.initialize-schema=always
spring.batch.jdbc.platform=postgresql
spring.batch.job.enabled=false
# 시스템 전반에서 사용되는 datasource 설정값
spring.datasource.global.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.global.username=postgres
spring.datasource.global.password=postgres
spring.datasource.global.driver-class-name=org.postgresql.Driver
# 스프링 배치용 datasource 설정값
spring.datasource.batch.url=jdbc:postgresql://localhost:5432/postgres?currentSchema=spring_batch
spring.datasource.batch.username=postgres
spring.datasource.batch.password=postgres
spring.datasource.batch.driver-class-name=org.postgresql.Driver
currentSchema=spring_batch
부분이 postgres jdbc url 이 제공하는 옵션입니다. 이렇게 함으로서 dataSource 의 default 스키마를 변경할 수 있습니다.이후 아래와 같이 @Configuration
설정 클래스를 생성해줍니다.
package me.dailycode.springbatch.datasource;
import org.springframework.boot.autoconfigure.batch.BatchDataSource;
import org.springframework.boot.autoconfigure.batch.BatchTransactionManager;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.support.JdbcTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
@Configuration
public class DataSourceConfiguration {
@Bean
@ConfigurationProperties("spring.datasource.global")
public DataSourceProperties globalDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@Primary
public DataSource dataSource() {
return globalDataSourceProperties()
.initializeDataSourceBuilder()
.build();
}
@Bean
@Primary
public PlatformTransactionManager transactionManager() {
return new JdbcTransactionManager(dataSource());
}
@Bean
@ConfigurationProperties("spring.datasource.batch")
public DataSourceProperties batchDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@BatchDataSource
public DataSource batchDataSource() {
return batchDataSourceProperties()
.initializeDataSourceBuilder()
.build();
}
@Bean
@BatchTransactionManager
public PlatformTransactionManager batchTransactionManager() {
return new JdbcTransactionManager(batchDataSource());
}
}
만약 default 스키마의 설정을 Configuration 클래스 코드에서 작성하고 싶다면
아래처럼 @BatchDataSource
Bean 메소드 내용을 바꾸시면 됩니다.
@Bean
@BatchDataSource
public DataSource batchDataSource() {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setDriverClassName(batchDataSourceProperties().getDriverClassName());
hikariConfig.setJdbcUrl(batchDataSourceProperties().getUrl());
hikariConfig.setUsername(batchDataSourceProperties().getUsername());
hikariConfig.setPassword(batchDataSourceProperties().getPassword());
hikariConfig.setSchema("spring_batch"); // default 스키마 지정!
return new HikariDataSource(hikariConfig);
}
이게 필요한 이유는 모든 DB 관련 jdbc 구현체가
postgresql jdbc
처럼currentSchema
옵션을 주는 게 아니기 때문입니다.