문제
멀티 데이터소스 환경에서, 스프링 배치 5 버전에서는 특별한 설정을 하지 않으면 @Primary 데이터소스를 가져온다. 하지만 나는 Primary가 아닌 다른 데이터소스를 가져오려고 설정을 했더니 Job이 실행이 되지 않고, 또 application.yml 파일에 적어둔 배치 설정도 적용이 되지 않던 문제가 있었다.
상황
# application.yml
spring:
batch:
jdbc:
platform: postgresql # DBMS, 안 적으면 자동으로 탐색
# 실행할 스키마, 배치 라이브러리 내부에 각 DBMS 별로 sql이 작성돼있다.
schema: classpath:org/springframework/batch/core/schema-postgresql.sql
initialize-schema: always # 항상 스키마를 초기화할 건지
원래 이렇게 application.yml을 작성해두고,
import lombok.RequiredArgsConstructor;
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.launch.support.RunIdIncrementer;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
@Slf4j(topic = "simple-job")
@Configuration
@RequiredArgsConstructor
public class SimpleJob {
private static final String JOB_NAME = "simple_job";
private final JobRepository jobRepository;
private final PlatformTransactionManager transactionManager;
@Bean(JOB_NAME)
public Job simpleJob() {
return new JobBuilder(JOB_NAME, jobRepository)
.start(simpleStep())
.incrementer(new RunIdIncrementer())
.build();
}
@Bean(JOB_NAME + "_step")
public Step simpleStep() {
return new StepBuilder(JOB_NAME + "_step", jobRepository)
.tasklet((contribution, chunkContext) -> {
log.info("Hello, world!");
return null;
}, transactionManager)
.build();
}
}
헬로 월드 로그를 출력하는 간단한 잡을 만들어두고 실행하면,
[main] simple-job : Hello, world!
이렇게 잘 출력이 된다.
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
@Configuration
@EnableScheduling
@EnableBatchProcessing(dataSourceRef = "subDataSourceProxy")
public class BatchConfig {
}
하지만 스프링 배치 5 버전에서 데이터소스를 변경하려면 @EnableBatchProcessing 어노테이션의 dataSourceRef 속성에 원하는 데이터소스 빈이름을 작성해주어야 한다.
하지만 이렇게 작성하고 실행을 하면 application.yml에 작성해 둔 설정이 적용이 안 되고, 잡도 실행이 되지 않는다.
이유는 스프링 배치 5 버전으로 올라오면서 기존의 설정 방법과 달라지고, @EnableBatchProcessing 어노테이션이 필수가 아닌 선택이 되었는데, 이 어노테이션을 사용하면 자동 설정들이 되지 않기 때문.
그래서 직접 설정을 해주고 또 잡도 실행시켜주어야 한다.
해결
// 커스텀으로 만든 DataSourceConfig
@Bean
public DataSourceInitializer batchDataSourceInitializer(@Qualifier("subDataSourceProxy") DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
if (isBatchTablesPresent(jdbcTemplate)) {
return null;
}
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.addScript(resourceLoader.getResource("classpath:org/springframework/batch/core/schema-postgresql.sql"));
DataSourceInitializer initializer = new DataSourceInitializer();
initializer.setDataSource(dataSource);
initializer.setDatabasePopulator(populator);
return initializer;
}
private boolean isBatchTablesPresent(JdbcTemplate jdbcTemplate) {
try {
jdbcTemplate.execute("SELECT 1 FROM BATCH_JOB_INSTANCE LIMIT 1");
return true;
} catch (Exception e) {
return false;
}
}
우선, DataSourceInitializer를 통해 원하는 데이터소스에 배치 메타데이터 테이블이 존재하지 않는다면 새로 테이블을 생성하도록 코드를 작성해 두었다.
import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameter;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersInvalidException;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Slf4j
@Component
@RequiredArgsConstructor
public class Launcher {
private final Job simpleJob;
private final JobLauncher jobLauncher;
@Scheduled(cron = "0/10 * * * * *", zone = "Asia/Seoul")
public void run() {
JobParameters jobParameters = new JobParameters(
Map.of("requestTime", new JobParameter<>(System.currentTimeMillis(), Long.class))
);
try {
jobLauncher.run(simpleJob, jobParameters);
} catch (JobExecutionAlreadyRunningException
| JobInstanceAlreadyCompleteException
| JobParametersInvalidException
| JobRestartException e) {
throw new RuntimeException(e);
}
}
}
위의 BatchConfig에서 @EnableScheduling을 해주었기 때문에 스케줄링을 할 수 있어서, @Scheduled로 하긴 했는데, 그냥 JobLauncher로만으로도 실행시킬 수 있다.
[scheduling-1] settle-job : Hello, world!
10초마다 잘 뜬다!
'TIL ✍️' 카테고리의 다른 글
TIL #123 : 배치 작업에는 꼭 정렬 하기 (0) | 2024.11.17 |
---|---|
TIL #122 : 읽기전용/쓰기전용DB를 @Transactional의 readOnly로 구분하기 (0) | 2024.11.16 |
TIL #120 : 멀티 데이터소스 환경에서 이중 커넥션 점유 막기 (1) | 2024.11.15 |
TIL #119 : Jacoco 테스트 커버리지 항목에 롬복이 생성한 코드 무시하기 (0) | 2024.11.13 |
TIL #118 : 롬복 @RequiredArgsConstructor 사용 시 @Qualifier 사용 불가 해결하기 (0) | 2024.11.13 |