动态数据源入门
1. 一句话简介
动态数据源(Dynamic DataSource)是指在 Spring Boot 应用中,不在启动时把数据源全部写死,而是在运行时根据请求上下文动态选择、创建和销毁数据库连接池的一种技术。本模块通过在 HTTP 请求中传入 Datasource-Config-Id 头,由 AOP 切面读取该值写入 ThreadLocal,动态数据源再根据线程绑定的 ID 返回对应数据库的连接,从而解决「一个应用同时访问多个数据库,且数据源不固定」的问题。
其核心做法是自定义一个继承 HikariDataSource 的 DynamicDataSource 类,重写 getConnection() 方法:先从 ThreadLocal 中取当前线程要使用的数据源 ID,再从数据源持有者(DatasourceHolder)缓存中查找或创建对应的连接池,并配合 @DefaultDatasource 注解强制部分接口只能走默认数据源。它把「多数据源 + 动态切换 + 连接池生命周期管理」整合进同一套机制,业务代码无需感知底层切换逻辑。
2. 什么时候使用
✅ 适用场景
- 运行时注册数据源:数据源是动态增加/删除的,不能在
application.yml里写死,需要像本模块那样通过POST /config接口运行时加入新的数据源,无需重启应用。 - 请求级数据源路由:同一套用户查询接口(如本模块的
GET /user)需要根据请求参数或请求头落到不同的库上,且切换是「一次性、无状态」的。 - 多租户/多客户数据隔离:不同租户或客户的数据放在不同数据库,按请求维度(如 Header 中的租户 ID)路由到各自的库,且租户数量可变。
- 读写分离/报表透出:简单地把读操作路由到只读从库,或把某一类查询路由到独立的分析库,且运行中会新增/下线库实例。
- 控制连接池资源占用:数据源按需懒加载,长时间未使用(如本模块 10 分钟)自动关闭连接池释放资源,需要精细管理连接生命周期。
❌ 不适用/需谨慎
- 数据源数量固定且较少(如 2~3 个):数据源是预先确定的稳定集合,用传统多数据源配置(每个
DataSource+SqlSessionFactory分开配置)更直观、更简单,无需引入 AOP 和 ThreadLocal 的切换复杂度。 - 需要跨库分布式事务:动态数据源把事务绑定在单一连接上,无法在多个动态数据源之间保证原子性,分布式事务应交给 Seata 等专门方案。
- 高并发大流量生产环境:本模块的方案偏「轻量演示」,大量请求并发切换、频繁创建/销毁连接池会带来性能和资源风险,生产环境建议评估成熟框架的分片/路由能力。
- 查询路由规则复杂(如按分片键取值路由):动态数据源只做「按 ID 整体路由」,不做字段级分片,分库分表应采用 ShardingSphere 等方案。
- 需要跨线程传递切换状态:
DynamicDataSource依赖ThreadLocal,异步任务、线程池、响应式(WebFlux)环境下状态无法跨线程传递,线程复用还可能有脏数据风险。
3. 常见业务场景
多租户 SaaS 应用:多个租户数据分库存储,租户数量动态变化。请求通过租户 ID 定位数据源,新增租户时只需新增一条数据源配置即可,对应本模块 DatasourceConfigController 的增删接口 + Datasource-Config-Id 请求路由。
运营后台多库数据查询:后台需要从多个独立业务库查询统计数据,用户可指定查哪个库。同一份查询逻辑(如本模块 GET /user 调 selectAll())通过切换 Header 即可在不同库间切换,无需为每个库写一套查询。
数据源配置管理系统:系统自身需要把「数据源连接信息」作为数据管理起来,支持在线注册、下线。本模块将数据源配置持久化在默认库的 datasource_config 表中,启动时加载进缓存,运行期通过接口增删——正是这类「动态运维」需求的示范。
低频/临时库 + 连接池回收:整体数据源可能很多但使用分散,许多库并不常被访问。本模块用 DatasourceHolder + DatasourceScheduler 定时清理超时未用的连接池,避免长时间占用大量连接,适合「库多但用得少」的场景。
多套环境共用的同一套服务:同一套代码部署需要对接不同环境的多套数据库(开发、测试、压测库),用请求头指定环境对应的数据源,便于联调和数据对比验证。
4. 同类技术对比
| 技术 | 数据源切换方式 | 运行时动态注册 | 分库分表 | 学习成本 | 适用规模 | 备注 |
|---|---|---|---|---|---|---|
| 传统多数据源(本模块的选型对比) | 静态配置,按数据源单独绑定 SqlSessionFactory | 不支持,需重启 | 不支持 | 低 | 数据源少且固定 | 最直观,适合 2~3 个固定库 |
| 自定义动态数据源(本模块做法,继承 HikariDataSource) | 运行时按 ID 检索,AOP + ThreadLocal 实现请求级切换 | 支持,可在线增删连接池 | 不支持,仅整体路由 | 中 | 中小规模、库多但用量分散 | 灵活、可控性强,适合演示与轻量化场景 |
| AbstractRoutingDataSource(Spring 官方) | 基于路由键 lookup 目标数据源 | 需配合动态注册实现,通常不以运行期常变为主 | 不支持 | 低-中 | 中小规模、路由键固定集合 | 官方抽象,约束较固定,缺少连接池回收管理 |
| ShardingSphere-JDBC(分库分表生态) | 按分片规则解析 SQL,字段级路由 | 通过配置/规则动态管理 | 支持 | 高 | 大规模、分库分表复杂路由 | 业界主流,功能全但引入成本高 |
| 代理中间件(如 MyCat、Proxysql) | SQL 代理层路由,应用无感知 | 在中间件侧管理 | 支持 | 应用侧低、运维侧高 | 大集群、跨应用 | 对应用透明,但引入独立运维组件 |
选型建议
- 数据源数量少且固定(2~3 个)、懒得分片时,优先用传统多数据源直接配置,维护成本最低。
- 需要「运行时动态注册 + 请求级切换 + 连接池按需回收」,且库量中等、无复杂分片规则时,自定义动态数据源(本模块做法)或 AbstractRoutingDataSource 已足够,前者更灵活、后者更轻守约束。
- 一旦出现「按分片键取值路由」「大表水平拆分」「SQL 级路由」等诉求,应直接转向 ShardingSphere-JDBC 或代理中间件,动态数据源只做整体路由、不适合字段级分片。
- 涉及多数据源间的跨库事务,动态数据源与单连接事务绑定,无法胜任,应叠加 Seata 等分布式事务方案。