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. 잘 알고 잘 사용해보자.
'📕 Spring Framework > Spring 개념 정리' 카테고리의 다른 글
Bean 등록에 대한 재고 (1) | 2024.10.12 |
---|---|
[Spring data JPA] N+1 문제 해결 (2) | 2023.02.28 |
[Reactive Programming] 비동기-논블로킹 프로그래밍 (2) | 2023.02.11 |
[@DataJpaTest] h2 인메모리 db를 이용한 테스트 설정 방법 (0) | 2022.12.21 |
WebFlux는 무엇이고, 왜 나왔고, 언제 쓰이는가? (0) | 2022.08.31 |