当前位置: 首页 > news >正文

第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 层、数据访问层的实际应用,以及虚拟线程的对比。


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

http://www.jsqmd.com/news/1380509/

相关文章:

  • 从CTF到企业实战:构建分层日志分析框架与ELK/SIEM工具链
  • C++形参默认值:语法规则、编译器实现与实战避坑指南
  • VMware ESXi虚拟机CentOS 7系统盘扩容实战:从VMDK到LVM全流程详解
  • 计算机保研预推免笔面试全攻略:从算法真题到项目深挖
  • LeetCode最长连续序列哈希表解法详解
  • 自我认知重构:从系统思维到行为调试的工程化实践
  • Excel专业修约:四舍六入五成双的VBA与公式实现
  • VC6.0工程文件修复工具:解析、诊断与自动修复.dsp/.dsw文件
  • AI编程助手上下文管理:从Token原理到实战解决Claude Code“失忆”问题
  • 【非标自动化】2、认识元器件(固态继电器)
  • CS:S武器手感调优指南:从视图模型到网络参数的全面配置
  • 福州大学控制类考研专业深度解析:学硕、专硕与交叉方向如何选择
  • Agent Harness框架:构建生产级AI Agent的工程化实践指南
  • DeepSpeed ZeRO-3保存Checkpoint后OOM:原理、诊断与解决方案
  • 兼容适配:M-Robots 如何实现 ROS 生态无缝迁移,降低替换成本
  • AI术语解析:从Agent到向量数据库,穿透技术黑话迷雾
  • 芯片测试:从DFT设计到量产良率管理的系统工程实践
  • RCE漏洞挖掘实战:从原理到Payload构造与绕过技巧
  • 利用ShellcodePack实现DLL与COM劫持:高隐蔽性Shellcode加载与持久化技术详解
  • Logo 设计工具记录:多款智能 Logo 生成工具能力边界整理
  • Python读取TIF文件全攻略:从Pillow到rasterio的实战选型
  • Oracle 19c Linux静默安装实战:从系统调优到建库配置全解析
  • 2026模板小程序开发服务商哪家更新快?运维有保障才是真的好!
  • 2026贵阳外墙漏水避坑指南 - 企业资讯
  • Micrometer 系列【41】链路追踪:Micrometer Tracer 接口
  • STM32输入捕获功能详解:从原理到实战的频率测量指南
  • vue基础(第四章 Pinia)
  • 基于Kimi Work构建300并发AI Agent系统,实现就业市场智能侦察
  • 【RustyML入门】3.2. 全连接层与激活函数
  • 深入解析PN结:从半导体基础到二极管特性与应用