๐Ÿ“• Spring Framework/Spring ๊ฐœ๋… ์ •๋ฆฌ

Spring boot multi datasource ๋“ฑ๋ก ์‹œ ์ฃผ์˜ ์‚ฌํ•ญ

GroovyArea 2024. 9. 18. 15:11

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. ์ž˜ ์•Œ๊ณ  ์ž˜ ์‚ฌ์šฉํ•ด๋ณด์ž.

๋ฐ˜์‘ํ˜•