개발/BACKEND

spring batch에서 DataSource 분리

uhanuu 2024. 7. 10. 10:55

이번에 JPA를 통해서 외부 DB에서 데이터를 Read/Write를 수행하지만 Spring Batch의 메타 테이블은 분리하고 싶었습니다.

Spring batch 5.0에서 @EnableBatchProcessing의 새로운 속성이 생겨 쉽게 구현할 수 있을 줄 알았습니다.

 

⛔️ 주의점

SpringBoot 3.0부터 @EnableBatchProcessing 혹은 DefaultBatchConfiguration을 상속받아 사용하면 AutoConfiguration이 동작하지 않습니다.

DefaultBatchConfiguration

@EnabledBatchProcessing이 내부적으로 빈을 등록하던 것을 DefaultBatchConfiguration 을 통해 명시적인 커스텀을 할 수 있다. (참조)

 

좀 더 정확하게 말하면 BatchAutoConfiguration이 동작하지 않습니다.

BatchAutoConfiguration 클래스에 달려있는 @ConditionalOnMissingBean 때문에 설정으로 Bean들이 등록되지 않습니다.

 

🥹 문제점들을 하나씩 해결해보자

메타 테이블이 생기지 않는다

application.properties에 spring.batch.initialize-schema=ALWAYS를 적용해도 메타 테이블이 생기지 않는다.

 

 

org.springframework.batch.core에 들어있는 schema 파일에서 DataSource로 사용하는 DMBS를 찾아서 초기 테이블 setting을 해주면 된다.

자동으로 배치가 실행되지 않는다.

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

Spring Batch 로 다중 Data Source 접근하기(매우 간단 주의) 들어가기에 앞서 medium.com

JPA Multiple DataSource 설정 방법 JPA 를 이용하여 다중 DB 를 설정하고 접속테스트 까지 진행해 보도록 하자 velog.io