이번에 JPA를 통해서 외부 DB에서 데이터를 Read/Write를 수행하지만 Spring Batch의 메타 테이블은 분리하고 싶었습니다.
Spring batch 5.0에서 @EnableBatchProcessing의 새로운 속성이 생겨 쉽게 구현할 수 있을 줄 알았습니다.
- Spring Batch가 구성해야 하는 dataSource 및 transactionManager를 지정할 수 있습니다.
⛔️ 주의점
SpringBoot 3.0부터 @EnableBatchProcessing 혹은 DefaultBatchConfiguration을 상속받아 사용하면 AutoConfiguration이 동작하지 않습니다.
DefaultBatchConfiguration
@EnabledBatchProcessing이 내부적으로 빈을 등록하던 것을 DefaultBatchConfiguration 을 통해 명시적인 커스텀을 할 수 있다. (참조)
- 또한 여러 job이 있는 경우 spring.batch.job.name 속성을 통해서 시작 시 어떤 작업을 실행해야 하는지 지정해야 합니다.
java -jar [file명].jar --job.name=[job명] --version=[version]
다음과 같이 jar를 실행시켜주면 되고 version의 경우 꼭 사용하지 않고 다른 용어로 사용해도 된다. 스프링 배치에서 실행 파라미터를 관리하고 있어서 중복된 파라미터 사용시 배치 실행 도중 실패한 경우 실패한 지점부터 다시 실행시켜주고 이미 완료된 job의 경우 실패한다. (참조)
좀 더 정확하게 말하면 BatchAutoConfiguration이 동작하지 않습니다.
BatchAutoConfiguration 클래스에 달려있는 @ConditionalOnMissingBean 때문에 설정으로 Bean들이 등록되지 않습니다.
🥹 문제점들을 하나씩 해결해보자
메타 테이블이 생기지 않는다
application.properties에 spring.batch.initialize-schema=ALWAYS를 적용해도 메타 테이블이 생기지 않는다.
- org.springframework.batch.core에 들어있는 schema 파일에서DataSource로 사용하는 DMBS를 찾으면 된다.
data.sql을 하나 만들어서 아래 script를 복사해서 초기 테이블을 세팅 해줬다.
자동으로 배치가 실행되지 않는다.
BatchAutoConfiguration이 동작하지 않았기 때문에 jobLauncherApplicationRunner를 Bean으로 등록해주면 된다.
@Configuration
@EnableConfigurationProperties(BatchProperties::class)
class BatchConfig {
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.batch.job", name = ["enabled"], havingValue = "true", matchIfMissing = true)
fun jobLauncherApplicationRunner(
jobLauncher: JobLauncher, jobExplorer: JobExplorer,
jobRepository: JobRepository, properties: BatchProperties
): JobLauncherApplicationRunner {
val runner = JobLauncherApplicationRunner(jobLauncher, jobExplorer, jobRepository)
val jobName: String? = properties.job.name
if (!jobName.isNullOrEmpty()) {
runner.setJobName(jobName)
}
return runner
}
}
Quartz, Spring Scheduler와 같은 스케줄러를 통해서 사용 가능하다.
@EnableScheduling
@Component
@RequiredArgsConstructor
class MailReadScheduler(
val jobLauncher: JobLauncher,
val job: Job
) {
@Scheduled(fixedDelay = 30000)
fun startJob() {
val jobParameterMap = mapOf("requestDate" to JobParameter(OffsetDateTime.now().toString(), String::class.java))
val jobParameters = JobParameters(jobParameterMap)
val jobExecution: JobExecution = jobLauncher.run(job, jobParameters)
while (jobExecution.isRunning) {
println("isRunning....")
}
}
}
👻 DataSource 등록
JDBC로 데이터를 조회할 수 있었지만 기존 코드가 JPA를 사용해서 Batch를 돌리고 있었기 때문에 EntityManager의 DataSource 수정이 필요했습니다.
DataSource Bean으로 등록하기
@EnableTransactionManagement(proxyTargetClass = true)
@EnableJpaRepositories(
basePackageClasses = [GoogleRefreshToken::class, Article::class],
entityManagerFactoryRef = "serverEntityManagerFactory",
transactionManagerRef = "serverTransactionManager"
)
@Configuration
class DataSourceConfig {
@Bean
@Primary
@ConfigurationProperties("spring.datasource.default")
fun defaultDataSource(): DataSource {
return DataSourceBuilder.create().apply {
type(HikariDataSource::class.java)
}.build()
}
@Bean
@BatchDataSource
@ConfigurationProperties("spring.datasource.server")
fun serverDataSource(): DataSource {
return DataSourceBuilder.create().apply {
type(HikariDataSource::class.java)
}.build()
}
@Bean
fun serverEntityManagerFactory(
@Qualifier("serverDataSource") dataSource: DataSource
): LocalContainerEntityManagerFactoryBean {
return LocalContainerEntityManagerFactoryBean().apply {
persistenceUnitName = "serverEntityManager"
this.dataSource = dataSource
jpaVendorAdapter = HibernateJpaVendorAdapter()
setPackagesToScan("attraction.run.token", "attraction.run.article")
jpaDialect = HibernateJpaDialect()
}
}
@Bean
fun jpaTransactionManager(
@Qualifier("serverEntityManagerFactory") entityManagerFactory: EntityManagerFactory
): JpaTransactionManager {
return JpaTransactionManager().apply {
this.entityManagerFactory = entityManagerFactory
setJpaDialect(HibernateJpaDialect())
}
}
}
DataSource가 Bean으로 2개가 등록되어 있어 메타 테이블을 저장할 DataSource를 @Primary를 주었고 Read/Write 작업에서 사용되는 DataSource를 EntityManager로 지정해 Bean으로 등록했습니다.
@EnableBatchProcessing 사용하기
@Configuration
@EnableBatchProcessing(dataSourceRef = "defaultDataSource", transactionManagerRef = "serverTransactionManager")
class BatchConfig(
@Qualifier("serverEntityManagerFactory")
private val entityManagerFactory: EntityManagerFactory,
) {
@Bean
fun job(jobRepository: JobRepository, step: Step): Job {
return JobBuilder("job", jobRepository)
.start(step)
.build()
}
...
}
📚 Reference
- https://docs.spring.io/spring-batch/reference/whatsnew.html
- https://www.baeldung.com/spring-boot-configure-multiple-datasources
'개발 > Spring Boot' 카테고리의 다른 글
Spring Batch JpaItemWriter에서 List<Entity> 처리하기 (0) | 2024.05.25 |
---|---|
회원 탈퇴 로직 Spring Event로 처리하기 (0) | 2024.04.06 |
[Spring] 스레드 풀 (1) | 2024.01.29 |
토큰 재발급 로직을 테스트하면서 발생한 문제 (2) | 2024.01.18 |
Test에서 deleteAll과 deleteAllInBatch() (0) | 2023.07.18 |