본문 바로가기
📕 Spring Framework/Spring 개념 정리

Spring boot multi datasource 등록 시 주의 사항

by GroovyArea 2024. 9. 18.

Spring Boot Multi datasource 등록 시 주의 사항

최근 진행했던 프로젝트는 외부 휴무일 정보 API를 호출하여 DB에 적재하는 월배치용 Spring Batch 애플리케이션을 개발하는 것이었다.
회사에는 여러가지 DataBase가 있는데, 그 중 메인 DB를 사용하기로 했고, spring batch 용 DB는 따로 사용하게 되었다.
그래서 멀티 datasource bean 을 등록해줘야 했다.
그 과정에서 정말 기초적이지만 실수했던 부분을 설명해보겠다.

Multi Datasource

하나의 프로젝트에서 여러 개의 데이터베이스를 연결하는 것.

spring 에서는 기본적으로 yaml 파일을 통해 쉽게 datasource 구성이 가능하다.
따로 java 코드로 bean 을 등록할 필요가 없을 것이다.
하지만 여러 database 를 연결하고자 한다면, 우선 순위 database 를 지정하기 위해 코드로 configuration 작업을 진행해주는 것이 필요하다.

Multi Datasource 예제 코드

@Configuration
@ConditionalOnProperty(
    prefix = "~~company.db.units.core-batch",
    name = ["enabled"],
    havingValue = "true",
    matchIfMissing = false
)
@EnableJpaRepositories(
    basePackages = ["holiday.batch.infra.db.corebatch"],
    entityManagerFactoryRef = CORE_BATCH_ENTITY_MANAGER_FACTORY,
    transactionManagerRef = CORE_BATCH_TRANSACTION_MANAGER,
)
class CoreBatchDatabaseConfiguration : DatabaseConfigurationBase("core-batch") {

    @Bean(CORE_BATCH_DATA_SOURCE)
    @ConfigurationProperties(
        DatabaseUnitProperties.PROP_BINDING_PREFIX + "core-batch" + DatabaseUnitProperties.PROP_BINDING_SUFFIX_HIKARI
    )
    fun coreBatchDataSource(): DataSource = super.dataSource()

    @Bean(CORE_BATCH_ENTITY_MANAGER_FACTORY)
    fun coreBatchEntityManagerFactory(
        @Qualifier(CORE_BATCH_DATA_SOURCE) dataSource: DataSource
    ): LocalContainerEntityManagerFactoryBean = super.entityManagerFactory(dataSource)

    @Bean(CORE_BATCH_TRANSACTION_MANAGER)
    fun coreBatchTransactionManager(
        @Qualifier(CORE_BATCH_DATA_SOURCE) dataSource: DataSource,
    ): PlatformTransactionManager = JdbcTransactionManager(dataSource)
}

core-batch - spring batch 테이블을 위한 db 이다.
이를 위한 configuration 작업이다.

@Configuration
@ConditionalOnProperty(
    prefix = "~~conmpany.db.units.main",
    name = ["enabled"],
    havingValue = "true",
    matchIfMissing = false
)
@EnableJpaRepositories(
    basePackages = ["holiday.batch.infra.db.main"],
    entityManagerFactoryRef = MAIN_ENTITY_MANAGER_FACTORY,
    transactionManagerRef = MAIN_JPA_TRANSACTION_MANAGER,
)
class MainDatabaseConfiguration : DatabaseConfigurationBase("main") {

    @Bean(MAIN_DATA_SOURCE)
    @ConfigurationProperties(
        DatabaseUnitProperties.PROP_BINDING_PREFIX + "main" + DatabaseUnitProperties.PROP_BINDING_SUFFIX_HIKARI
    )
    fun mainDataSource(): DataSource = super.dataSource()

    @Bean(MAIN_ENTITY_MANAGER_FACTORY)
    fun mainEntityManagerFactory(): LocalContainerEntityManagerFactoryBean =
        super.entityManagerFactory(mainDataSource())

    @Bean(MAIN_JPA_TRANSACTION_MANAGER)
    fun mainTransactionManager(
        @Qualifier(MAIN_ENTITY_MANAGER_FACTORY) emf: EntityManagerFactory
    ): PlatformTransactionManager =
        super.transactionManager(mainDataSource(), emf)

    @Bean(MAIN_JDBC_TRANSACTION_MANAGER)
    fun mainJdbcTransactionManager(): PlatformTransactionManager {
        return JdbcTransactionManager(mainDataSource())
    }
}

main - main database 를 위한 configuration 작업이다.

@Configuration
@ComponentScan(basePackageClasses = [DatabaseAutoConfiguration::class])
@EnableConfigurationProperties(value = [DatabaseProperties::class])
@EnableJpaAuditing
class DatabaseAutoConfiguration

object DataBaseConstants {
    const val MAIN_DATA_SOURCE = "mainDataSource"
    const val MAIN_ENTITY_MANAGER_FACTORY = "mainEntityManagerFactory"
    const val MAIN_JPA_TRANSACTION_MANAGER = "mainJpaTransactionManager"
    const val MAIN_JDBC_TRANSACTION_MANAGER = "mainJdbcTransactionManager"

    const val CORE_BATCH_DATA_SOURCE = "coreBatchDataSource"
    const val CORE_BATCH_ENTITY_MANAGER_FACTORY = "coreBatchEntityManagerFactory"
    const val CORE_BATCH_TRANSACTION_MANAGER = "coreBatchTransactionManager"
}

현재 프로젝트 구조는 멀티 모듈 형태로 구성 되어 있기 때문에, auto-configuration 을 위해 component scan 을 진행해주었다.

문제점이 보이십니까?

위의 datasource 구성 작업의 문제점이 눈에 들어오는가?

위 설정 파일을 가지고 application 을 실행한다면 required a single bean, but (n) were found
대강 이런 예외 메시지가 발생하며 종료될 것이다.

당연히 bean 이름을 다르게 했는데, 무엇이 문제일까 할 것이다.
하지만 이는 어리석은 착각이다.

이 datasource 설정으로는,
spring boot 는 어떤 Datasource 를 메인으로 사용할지 모른다.

core-batch 가 메인인지, main 이 메인인지.. 모르는 것이다.
참 당연한 것이다.

해결 방법

문제 해결을 위해 어떤 datasource 가 메인인지 spring boot 에게 알려주어야 한다.
아무래도 spring batch 애플리케이션이기 때문에, batch 용 db 가 메인이 되어야 되지 싶다.

그래서 @Primary 애노테이션을 붙여주어 이걸 메인으로 사용하라 알려주었다.

@Configuration
@ConditionalOnProperty(
    prefix = "~~company.db.units.core-batch",
    name = ["enabled"],
    havingValue = "true",
    matchIfMissing = false
)
@EnableJpaRepositories(
    basePackages = ["holiday.batch.infra.db.corebatch"],
    entityManagerFactoryRef = CORE_BATCH_ENTITY_MANAGER_FACTORY,
    transactionManagerRef = CORE_BATCH_TRANSACTION_MANAGER,
)
class CoreBatchDatabaseConfiguration : DatabaseConfigurationBase("core-batch") {

    @Primary
    @Bean(CORE_BATCH_DATA_SOURCE)
    @ConfigurationProperties(
        DatabaseUnitProperties.PROP_BINDING_PREFIX + "core-batch" + DatabaseUnitProperties.PROP_BINDING_SUFFIX_HIKARI
    )
    fun coreBatchDataSource(): DataSource = super.dataSource()

    @Primary
    @Bean(CORE_BATCH_ENTITY_MANAGER_FACTORY)
    fun coreBatchEntityManagerFactory(
        @Qualifier(CORE_BATCH_DATA_SOURCE) dataSource: DataSource
    ): LocalContainerEntityManagerFactoryBean = super.entityManagerFactory(dataSource)

    @Primary
    @Bean(CORE_BATCH_TRANSACTION_MANAGER)
    fun coreBatchTransactionManager(
        @Qualifier(CORE_BATCH_DATA_SOURCE) dataSource: DataSource,
    ): PlatformTransactionManager = JdbcTransactionManager(dataSource)
}

이렇게 하면 정상적으로 애플리케이션은 실행 되고 batch 까지 정상적으로 실행하는 것을 확인할 수 있었다.

결론

기존에 @Primary 애노테이션을 사용하면서 개발할 일이 없었어서, 이걸 왜 달아줘야 하는지 몰랐었다.
multi datasource 구성을 할 일이 없었기도 했고.

하지만 가장 기본적이고도 당연한 bean 등록의 디테일을 놓쳤다는 생각에 역시 기본이 중요하다라는 생각을 해본다.
Primary. 잘 알고 잘 사용해보자.

반응형