三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

第50篇-多数据源集成

第50篇-多数据源集成

【Kotlin + Spring Boot 4 从零到架构师】第 50 篇:多数据源集成

本系列定位:零基础入门,从 Kotlin 语法一路到 Spring Boot 4 高级架构(DDD + Modulith),适合 Java 开发者转型,也适合纯新手系统学习。


本篇你将学到

  • 多数据源的应用场景
  • 多数据源配置策略
  • @Primary/@Qualifier的使用
  • 主库(PostgreSQL)+ 分析库(ClickHouse)的双数据源实战

学完本篇,你将能让 mini-shop 同时连接多个数据库,各司其职。


一、为什么需要多数据源

1.1 典型场景

场景数据源 A数据源 B
读写分离主库(写)从库(读)
业务 + 分析PostgreSQL(业务)ClickHouse(统计分析)
多租户主租户库独立租户库
迁移过渡旧数据库新数据库

1.2 mini-shop 场景

PostgreSQL(主库):商品、订单、用户 —— 高频读写 ClickHouse(分析库):订单统计、销量报表 —— 低频大量查询

下面是 mini-shop 多数据源架构总览:

ClickHouse 分析库

PostgreSQL 主库

mini-shop 应用

JPA / @Primary

NamedParameterJdbcTemplate / @Qualifier

业务 Service

分析 AnalyticsService

订单表

商品表

用户表

订单统计

销量报表

二、配置两个数据源

2.1 添加依赖

dependencies{implementation("org.springframework.boot:spring-boot-starter-data-jpa")runtimeOnly("org.postgresql:postgresql")runtimeOnly("com.clickhouse:clickhouse-jdbc:0.7.0")}

2.2 application.yml

spring:datasource:# 主数据源(PostgreSQL)—— 业务数据url:jdbc:postgresql://localhost:5432/mini_shopusername:postgrespassword:postgres123driver-class-name:org.postgresql.Driverhikari:pool-name:PrimaryPool# 自定义分析数据源配置analytics:datasource:url:jdbc:clickhouse://localhost:8123/mini_shop_analyticsusername:defaultpassword:""driver-class-name:com.clickhouse.jdbc.ClickHouseDriverhikari:pool-name:AnalyticsPoolmaximum-pool-size:5

2.3 主数据源配置

packagecom.example.minishop.configimportorg.springframework.boot.autoconfigure.jdbc.DataSourcePropertiesimportorg.springframework.boot.context.properties.ConfigurationPropertiesimportorg.springframework.boot.jdbc.autoconfigure.DataSourcePropertiesasBootDataSourcePropertiesimportorg.springframework.context.annotation.Beanimportorg.springframework.context.annotation.Configurationimportorg.springframework.context.annotation.Primaryimportorg.springframework.data.jpa.repository.config.EnableJpaRepositoriesimportjavax.sql.DataSource@Configuration@EnableJpaRepositories(basePackages=["com.example.minishop.product","com.example.minishop.order","com.example.minishop.user"],entityManagerFactoryRef="primaryEntityManagerFactory",transactionManagerRef="primaryTransactionManager")classPrimaryDataSourceConfig{@Bean@Primary@ConfigurationProperties("spring.datasource")funprimaryDataSourceProperties():DataSourceProperties{returnDataSourceProperties()}@Bean@PrimaryfunprimaryDataSource():DataSource{returnprimaryDataSourceProperties().initializeDataSourceBuilder().build()}}

2.4 分析数据源配置

packagecom.example.minishop.configimportorg.springframework.boot.context.properties.ConfigurationPropertiesimportorg.springframework.context.annotation.Beanimportorg.springframework.context.annotation.Configurationimportorg.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplateimportjavax.sql.DataSource@ConfigurationclassAnalyticsDataSourceConfig{@Bean@ConfigurationProperties("spring.analytics.datasource")funanalyticsDataSourceProperties():DataSourceProperties{returnDataSourceProperties()}@Bean("analyticsDataSource")funanalyticsDataSource():DataSource{returnanalyticsDataSourceProperties().initializeDataSourceBuilder().build()}@Bean("analyticsJdbcTemplate")funanalyticsJdbcTemplate(@Qualifier("analyticsDataSource")dataSource:DataSource):NamedParameterJdbcTemplate{returnNamedParameterJdbcTemplate(dataSource)}}

2.5 使用分析数据源

@ServiceclassAnalyticsService(@Qualifier("analyticsJdbcTemplate")privatevalanalyticsJdbc:NamedParameterJdbcTemplate){/** * 查询每日销售统计 */fungetDailySalesStats(startDate:String,endDate:String):List<DailySalesStat>{valsql=""" SELECT toDate(created_at) AS date, count() AS order_count, sum(total_amount) AS total_revenue, avg(total_amount) AS avg_order_value FROM orders WHERE created_at BETWEEN :start AND :end AND status = 'PAID' GROUP BY date ORDER BY date """.trimIndent()valparams=mapOf("start"tostartDate,"end"toendDate)returnanalyticsJdbc.query(sql,params){rs,_->DailySalesStat(date=rs.getString("date"),orderCount=rs.getLong("order_count"),totalRevenue=rs.getBigDecimal("total_revenue"),avgOrderValue=rs.getBigDecimal("avg_order_value"))}}}dataclassDailySalesStat(valdate:String,valorderCount:Long,valtotalRevenue:BigDecimal,valavgOrderValue:BigDecimal)

下面是双数据源配置与注入的完整流程:

Service 层

Repository 层

Bean 定义

配置层

application.yml
spring.datasource (PG)
spring.analytics.datasource (CH)

PrimaryDataSourceConfig
@ConfigurationProperties
@Primary

AnalyticsDataSourceConfig
@ConfigurationProperties

primaryDataSource (PG)
@Primary

analyticsDataSource (CH)

analyticsJdbcTemplate
NamedParameterJdbcTemplate

@EnableJpaRepositories
product / order / user

OrderService
自动注入 @Primary

AnalyticsService
@Qualifier 指定

三、关键注意事项

3.1 @Primary 的作用

当 Spring 容器中有多个DataSourceBean 时,@Primary标记的 Bean 是默认选择。Liquibase、Spring Data JPA 自动配置默认会用@Primary的数据源。

@Bean@Primary// ← 主数据源,Spring Boot 自动配置用这个funprimaryDataSource():DataSource{...}

3.2 @Qualifier 精确指定

// 不用 @Primary 时,必须用 @Qualifier 指定@ServiceclassAnalyticsService(@Qualifier("analyticsDataSource")// ← 精确指定分析数据源privatevalanalyticsDataSource:DataSource)

3.3 ClickHouse autoCommit 陷阱

ClickHouse 的 JDBC 驱动要求autoCommit = true(与 PostgreSQL 不同):

spring:analytics:datasource:hikari:auto-commit:true# ClickHouse 必须为 true

3.4 Liquibase 只在主数据源

spring:liquibase:change-log:classpath:db/changelog/db.changelog-master.yaml# Liquibase 默认用 @Primary 数据源,不需要额外配置

四、@Primary vs @Qualifier 决策

方式特点场景
@Primary全局默认选择主数据源(90% 用这个)
@Qualifier("name")精确指定分析数据源、特定场景

原则:主数据源用@Primary,其他数据源用@Qualifier显式注入。


下面是数据源选择决策流程图:

需要注入 DataSource

只有一个 DataSource Bean?

Spring 自动注入
无需额外注解

该数据源是
主业务数据源?

标记 @Primary
全局默认选择

注入点明确知道
要哪个数据源?

使用 @Qualifier('beanName')
精确指定

编译报错:
NoUniqueBeanDefinitionException

Liquibase / JPA 自动配置
默认使用 @Primary

手动指定注入
如 analyticsDataSource

本篇小结

知识点核心内容
多数据源业务库 + 分析库 / 读写分离
@Primary标记主数据源(默认选择)
@Qualifier精确指定注入哪个数据源
@ConfigurationProperties绑定各数据源配置
@EnableJpaRepositories指定 Repository 用哪个数据源
NamedParameterJdbcTemplate非 JPA 的数据源操作
ClickHouse autoCommit必须为true
Liquibase只在主数据源

下篇预告

第 51 篇:Kotlin 协程在 Spring Boot 中的应用

Spring Boot 4 支持协程 Controller!下一篇学习suspend函数在 Web 层、数据访问层的实际应用,以及虚拟线程的对比。


如果本篇内容对你有帮助,欢迎点赞收藏!有任何疑问,欢迎在评论区交流。

← 返回列表