news 2026/9/8 8:13:07

Spring Boot + JDBC多数据源配置实战:从双JdbcTemplate到动态路由

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot + JDBC多数据源配置实战:从双JdbcTemplate到动态路由

简介:Spring Boot与JdbcTemplate结合实现多数据源管理,是一份面向Java后端开发者的实用工程示例。资源围绕主、从两个数据源的创建与使用展开,演示了通过DataSourceBuilder构建Bean、在application配置中绑定不同prefix参数,并利用@Qualifier分别注入primary与secondary的JdbcTemplate;同时点出Spring Data JPA处理多数据源的扩展思路,适合需要连接多个数据库或希望规范数据访问分层的读者。压缩包共116个文件,以76个XML配置、16个Java源码、15个Class编译文件及少量properties等为主,整体仅66KB,结构紧凑,便于对照学习。已有303人学习浏览。从源码中可快速掌握多数据源配置的完整骨架:DataSourceConfig集中定义数据源与JdbcTemplate,DemoApplication提供可直接运行入口,Course/User模块按Dao、Service、Controller分层展示实际调用方式;对理解自动配置原理及大型项目数据库拆分均具参考价值,可直接作为多数据源开发的实战模板。 接手过一个老系统改造,业务方上来就提需求:报表库和业务库要分开,读写要分流,后续可能还要再接一个第三方库。说白了就是Spring Boot项目里配多个数据源,走JDBC这一层把连接管好。那时候网上搜“springboot jdbc 多数据源”,文章倒是不少,但要么讲MyBatis,要么讲JPA,真正把JDBC多数据源从配置到实战说透的没几篇。这篇文章就把我从零配置到上线排错的全过程捋一遍,讲清楚方案怎么选、配置怎么写、切换怎么做、坑怎么躲,适合正在做多库拆分、读写分离或者接外部数据源的后端开发参考。

1. 多数据源这件事,关键在思路拆解

1.1 什么场景下真的需要多数据源

多数据源不是炫技,通常来自几种真实需求。一种是业务库和报表库分离,业务写入主库,报表统计走独立库,避免大查询拖垮线上业务;另一种是读写分离,主库负责写,从库负责读,这是最常见的双数据源布局;还有一种是企业内部系统需要同时访问多个既有的业务库,比如订单库、用户库、日志库各自独立部署,应用层做数据聚合。

在这些场景里,Spring Boot默认的单数据源自动配置就不够用了。默认情况下,spring.datasource.*只配一个DataSource,JdbcTemplate也好、MyBatis也好,绑定的都是这个默认数据源。一旦涉及第二个库,就会发现连接串、用户名、密码、驱动全乱套,于是必须手动接管数据源配置,让应用能同时持有多个DataSource实例,并且在使用时能明确指定“这次查哪个库”。

1.2 方案选型对比:不盲从,先算账

实现多数据源的常见方案有几种,我列一下当时对比的思路。

方案一:直接创建多个JdbcTemplate Bean。简单直接,代码里注入哪个JdbcTemplate就用哪个库,场景固定、数据源不常切换时最省事。缺点是如果库的数量多,Bean也跟着多,管理起来略显繁琐。

方案二:MyBatis / MyBatis-Plus 多数据源框架。社区里成熟方案很多,动态数据源切换、注解控制、读写分离都封装好了,适合业务复杂的场景。但它的代价是引入了框架依赖,而且在多数据源事务、批处理操作上依然有不少暗坑(后面会讲到)。

方案三:AbstractRoutingDataSource动态路由。自己实现一个路由数据源,基于ThreadLocal在运行时动态决定走哪个数据源。灵活度高,对业务代码几乎无侵入,适合有动态切换需求的场景,比如按租户分库。缺点是需要自己维护路由上下文和清理逻辑。

我当时的需求是报表库和业务库固定分离,所以最终选了“方案一为主体,方案三做补充”的组合:固定的双数据源用两个JdbcTemplate,后续要动态切换的地方再单独抽象动态路由。这个思路对大多数中后台项目都适用,既不会过度设计,又保留了扩展空间。

2. 前期准备与配置要点

2.1 项目依赖与版本选择

如果你准备参照这篇文章动手,先确认基础环境。我这里用的是Spring Boot 2.7.x,JDK 1.8,数据源连接池是Spring Boot默认的HikariCP。依赖上只需要引入JDBC相关的starter,和实际使用的数据库驱动。

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>

一个提醒:Spring Boot 2.7.x默认管理的MySQL驱动是8.0.x,连接串和驱动类名要按新版的来(com.mysql.cj.jdbc.Driver),别再用老的com.mysql.jdbc.Driver。如果项目用的数据库是PostgreSQL、SQL Server或者国产数据库,驱动依赖替换一下即可,核心的DataSource配置逻辑不变。

版本这方面我踩过一次教训:有同事直接用Spring Boot 3.x配合JDK 8开发,结果启动报错,因为Spring Boot 3基于Jakarta EE,要求JDK 17起步,而且javax.*包要改成jakarta.*。如果你的团队对JDK版本没那么激进,乖乖用2.7.x最稳,搜资料的时候也几乎不会遇到兼容性障碍。

2.2 application.yml多数据源配置

配置文件的思路是:绕过Spring Boot的自动配置前缀,把多个数据源写在自定义的prefix下。我习惯用spring.datasource.primaryspring.datasource.secondary区分主从或业务库和报表库。

spring: datasource: primary: jdbc-url: jdbc:mysql://localhost:3306/business_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false username: root password: root123 driver-class-name: com.mysql.cj.jdbc.Driver secondary: jdbc-url: jdbc:mysql://localhost:3306/report_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false username: report_user password: report123 driver-class-name: com.mysql.cj.jdbc.Driver

注意一个细节:自定义数据源如果直接配url而不是jdbc-url,在部分版本的DataSourceBuilder下会识别不到,所以我一律写jdbc-url,避免低级问题。数据库连接串务必带上serverTimezone,尤其MySQL 8以上,否则会有时区相关的报错。

2.3 为什么不能用默认的DataSource自动配置

默认情况下,Spring Boot会读取spring.datasource.url并自动创建一个DataSource。一旦你自定义了多个DataSource的Bean,必须想办法“屏蔽”自动配置,否则会出现DataSource实例冲突或者JdbcTemplate被自动配置强行绑定到其中某一个上面。

处理办法有两种。最优雅的是在启动类上排除DataSourceAutoConfiguration

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) public class MultiDataSourceApplication { public static void main(String[] args) { SpringApplication.run(MultiDataSourceApplication.class, args); } }

另一种是只自定义DataSource Bean但不排除自动配置,这会让你陷入命名冲突的泥潭。真没必要折腾,直接在启动类上加排除最省心。我刚开始就是没加排除,结果JdbcTemplate自动注入到了默认的数据源上,怎么指定都没用,排查了一个下午。

3. 核心实现:配置类与JdbcTemplate

3.1 数据源配置类编写

多数据源的核心就是手写配置类,把两个DataSource的Bean都交给Spring管理。看代码:

@Configuration public class DataSourceConfig { @Bean(name = "primaryDataSource") @Primary @ConfigurationProperties(prefix = "spring.datasource.primary") public DataSource primaryDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "secondaryDataSource") @ConfigurationProperties(prefix = "spring.datasource.secondary") public DataSource secondaryDataSource() { return DataSourceBuilder.create().build(); } }

@Primary必须加在主数据源上。道理很简单:Spring在注入DataSource类型的对象时,如果遇到多个候选Bean,会优先选择标记@Primary的那个。如果不加,启动时会直接报NoUniqueBeanDefinitionException

DataSourceBuilder.create().build()会依据配置里的driver-class-namejdbc-url自动匹配对应的连接池类型,如果什么都不指定,Spring Boot默认用HikariCP。这里不需要手动new DruidDataSourcenew HikariDataSource,让Builder去做,代码更简洁。

3.2 JdbcTemplate Bean的创建与使用

有了DataSource,下一步给每个数据源都配一个JdbcTemplate。我希望代码里能明确指示“当前用的是哪个JdbcTemplate”,所以在使用的地方通过@Qualifier指定。

@Configuration public class JdbcTemplateConfig { @Bean(name = "primaryJdbcTemplate") public JdbcTemplate primaryJdbcTemplate(@Qualifier("primaryDataSource") DataSource dataSource) { return new JdbcTemplate(dataSource); } @Bean(name = "secondaryJdbcTemplate") public JdbcTemplate secondaryJdbcTemplate(@Qualifier("secondaryDataSource") DataSource dataSource) { return new JdbcTemplate(dataSource); } }

使用的时候,要么用@Autowired@Qualifier,要么直接构造器注入按名字匹配:

@Service public class ReportService { private final JdbcTemplate primaryJdbcTemplate; private final JdbcTemplate secondaryJdbcTemplate; public ReportService(@Qualifier("primaryJdbcTemplate") JdbcTemplate primaryJdbcTemplate, @Qualifier("secondaryJdbcTemplate") JdbcTemplate secondaryJdbcTemplate) { this.primaryJdbcTemplate = primaryJdbcTemplate; this.secondaryJdbcTemplate = secondaryJdbcTemplate; } public void queryBusinessAndReport() { List<Map<String, Object>> businessList = primaryJdbcTemplate.queryForList("select * from t_order"); List<Map<String, Object>> reportList = secondaryJdbcTemplate.queryForList("select * from t_report"); // 业务处理... } }

这样两个JdbcTemplate各管一个库,互不干扰。对绝大多数“一个业务库+一个报表库”的场景来说,这套配置已经够用了。

3.3 动态切换数据源:用AbstractRoutingDataSource补扩展位

如果后续还要按租户切库,或者某些请求需要动态选择数据源,继续写固定的Bean就捉襟见肘了。这时候可以补一个动态路由的方案。

先创建一个路由数据源类,继承AbstractRoutingDataSource

public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getDataSource(); } }

再写一个ThreadLocal工具类,保存当前线程要使用的数据源标识:

public class DataSourceContextHolder { private static final ThreadLocal<String> CONTEXT = new ThreadLocal<>(); public static void setDataSource(String dataSource) { CONTEXT.set(dataSource); } public static String getDataSource() { return CONTEXT.get(); } public static void clear() { CONTEXT.remove(); } }

最后把DynamicDataSource注册成一个Bean,它本身也实现了DataSource接口,所以可以作为一个整体被注入使用:

@Bean public DynamicDataSource dynamicDataSource(@Qualifier("primaryDataSource") DataSource primaryDataSource, @Qualifier("secondaryDataSource") DataSource secondaryDataSource) { DynamicDataSource dynamicDataSource = new DynamicDataSource(); Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put("primary", primaryDataSource); targetDataSources.put("secondary", secondaryDataSource); dynamicDataSource.setTargetDataSources(targetDataSources); dynamicDataSource.setDefaultTargetDataSource(primaryDataSource); return dynamicDataSource; }

在需要切换的地方调用DataSourceContextHolder.setDataSource("secondary"),搞定后调用clear()清掉上下文,防止线程池复用导致串库。之前有同事忘了清,测试环境数据一会儿对一会儿错,查了半天才发现是ThreadLocal没清理,这个细节务必放心里。

4. 事务与连接管理的那些坑

4.1 跨库事务是真的难,别硬来

多数据源环境下,@Transactional不再像单数据源那样“万能”。默认情况下,一个事务管理器只绑定一个数据源,你在方法上写@Transactional,Spring会找PlatformTransactionManager类型的Bean,如果项目里配置了多个事务管理器,必须显式指定用哪个。

@Bean(name = "primaryTransactionManager") public DataSourceTransactionManager primaryTransactionManager(@Qualifier("primaryDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean(name = "secondaryTransactionManager") public DataSourceTransactionManager secondaryTransactionManager(@Qualifier("secondaryDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); }

使用时就变成:

@Transactional(transactionManager = "primaryTransactionManager") public void updateBusiness() { primaryJdbcTemplate.update("update t_order set status = 1 where id = ?", id); }

如果你硬要在同一个方法里同时写两个库,还要求原子性,那问题就复杂了。两个独立的事务管理器管的是两个独立连接,任何一个阶段报错,另一个库已提交的数据回滚不了。这种情况我有话直说:跨库强一致事务别指望Spring注解解决,要么引入分布式事务中间件,要么从业务设计上规避(比如先写主库,主库成功后异步同步到从库/报表库,再配合对账补偿)。老老实实接受“最终一致性”往往比硬追求强一致更明智。

4.2 连接池参数要单独调,别用默认值

多数据源环境下,连接池参数不再统一,primaryDataSourcesecondaryDataSource都应该单独配置。HikariCP的默认配置偏保守,如果报表库有大批量聚合查询,并发稍高一点就会出现连接等待超时。

我一般会在每个自定义数据源的配置里补上Hikari的独立配置:

spring: datasource: primary: jdbc-url: jdbc:mysql://localhost:3306/business_db?... username: root password: root123 driver-class-name: com.mysql.cj.jdbc.Driver hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 idle-timeout: 600000 secondary: jdbc-url: jdbc:mysql://localhost:3306/report_db?... username: report_user password: report123 driver-class-name: com.mysql.cj.jdbc.Driver hikari: maximum-pool-size: 50 minimum-idle: 10 connection-timeout: 30000 idle-timeout: 600000

报表库并发高,连接池就给大一点;业务库连接宝贵,就控制在合理范围。连接池满了之后,报错通常是Connection is not available, request timed out after 30000ms,看到这个错误第一反应就该去查连接池配置,而不是去查代码逻辑。

4.3 JDBC连接资源必须手动释放

使用JdbcTemplate时,框架内部已经帮你做了连接释放,问题不大。但如果你手写ConnectionPreparedStatementResultSet,记住一定要在finally块里关闭资源,或者直接用try-with-resources语法。我遇到过不止一次:某段代码里手动获取连接,忘记释放,导致连接池耗尽,数据库连接数拉满,最后整库查询卡死。用JdbcTemplate就能规避大部分这类问题,这也是我推荐JDBC场景直接用JdbcTemplate而不是裸写JDBC的原因。

5. 常见问题与排查实录

5.1 问题速查表

问题现象大概率原因解决思路
启动报错NoUniqueBeanDefinitionException存在多个DataSource或JdbcTemplate Bean,没有指定主次主数据源加@Primary,注入时用@Qualifier明确指定Bean名称
报错name jdbc is not bound in this context配置中用了JNDI方式(如spring.datasource.jndi-name),但容器里根本没有对应JNDI资源改为常规的jdbc-url连接配置,别在标准Spring Boot应用里用JNDI
报错No suitable driver found for jdbc:mysql://...驱动依赖缺失,或驱动类名和连接串不匹配检查pom依赖,确认MySQL驱动版本;驱动类用com.mysql.cj.jdbc.Driver
两个库的数据错乱ThreadLocal未清理,或非@Primary数据源被自动装配误用动态切换后必须在finally中执行DataSourceContextHolder.clear()
连接超时Connection is not available连接池太小,SQL慢,或连接泄漏调大maximum-pool-size,排查慢SQL和泄漏连接
使用事务时部分数据回滚不了跨库事务,不同事务管理器各自管理各自连接重新设计事务边界,或引入分布式事务组件

5.2 一个MyBatis-Plus用户的“SaveOrUpdateBatch多数据源”问题

热搜词里有一条“mybatis的saveorupdatebatch多数据源的问题”,我顺手说一下。有人在使用MyBatis-Plus时调saveOrUpdateBatch,在配置了动态数据源的情况下偶尔报错或数据进了错误的库。原因多半是批量操作里数据源切换的时机和事务绑定对不上:Spring的事务管理器在事务开启时就确定了连接,如果动态数据源切换发生在事务执行中间,连接已经持有旧数据源的引用,切换就失效了。

这类问题的解决思路是:把多数据源场景下的批量写入拆成两部分,先确定数据源再开启事务,不要在一个事务里来回切库。如果必须批处理,建议每个库单独走一批,不要在同一个循环体里对两个库交替写入。

5.3 排查技巧:开启Hikari的日志和SQL输出

真出问题不要靠猜,把SQL和连接获取日志打开,问题定位会快很多。在application.yml里加一行:

logging: level: org.springframework.jdbc.core.JdbcTemplate: DEBUG

这样JdbcTemplate执行的SQL参数都会打到日志里,可以根据日志确认当前线程用的到底是哪个数据源、哪条SQL。我排查串库问题的时候就是靠这个日志,一眼看到业务方法里打印出来的是报表库的SQL,才反推回去发现是ThreadLocal没清理。

6. 一些实操心得与建议

多数据源配置本身不复杂,复杂的是使用场景和边界管理。我现在的惯例是:固定双库用双JdbcTemplate Bean,简单清楚;动态场景才上AbstractRoutingDataSource,并且把DataSourceContextHolder的清理逻辑封装到切面里统一处理,避免项目里散落一地的try-finally代码。

另外,多数据源一定要和运维对齐连接信息,包括账号权限、网络白名单、防火墙策略等。之前有次上线后应用连不上报表库,排查很久发现是报表库账号只授权了本机访问,应用服务器IP没进白名单。这个问题和代码无关,但比代码问题更难排查。提前把环境配置确认好,能省掉大把时间。

如果你也是刚接手多数据源改造,建议先画一张简单的表,理清每个数据源对应的库、业务场景、连接池大小、事务边界,再动手写代码。配置类的代码就那些,最怕的是需求没理清就开写,最后代码和业务归属全乱套。希望这篇实战记录能帮你少走一些弯路。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 8:12:56

Rust重写微服务通信与数据同步:高可用异步架构实战

去年我们团队用RUST重写了一套微服务间的通信与数据同步系统&#xff0c;从最初的gRPC接口、异步任务调度&#xff0c;到连接池管理、增量数据同步、高可用切换&#xff0c;前前后后折腾了大半年。这期间踩了不少坑&#xff0c;也沉淀了不少经验&#xff0c;今天把整条技术链路…

作者头像 李华
网站建设 2026/9/8 8:12:08

HTML+CSS制作“我的家乡”网页模板:从零到完整页面的前端基本功

简介&#xff1a;HTMLCSS模板『我的家乡』是一套以地域文化为主题的响应式网页制作模板&#xff0c;适合网页设计初学者、前端学习者以及需要快速搭建家乡主题展示页的开发者使用。模板通过HTML定义页面结构、CSS控制视觉样式&#xff0c;完整覆盖首页、文化、历史、特产、名人…

作者头像 李华
网站建设 2026/9/8 8:11:32

Altium Designer许可证选型与部署:单机版和网络版怎么选

前阵子帮一家客户做Altium Designer的许可证规划&#xff0c;对方采购开口就问&#xff1a;"我们8个工程师&#xff0c;买5个网络版够不够&#xff1f;"这个问题听起来简单&#xff0c;背后其实牵扯到并发建模、网络拓扑、出差场景和预算分摊&#xff0c;答不好要么买…

作者头像 李华
网站建设 2026/9/8 8:06:17

Linux网卡配置全攻略:从入门到生产环境稳定调优

1. 项目概述与核心需求解析1.1 网卡配置到底在配什么干过Linux服务器运维的人都知道&#xff0c;网卡配置这件事看着简单&#xff0c;真上手到处是坑。搜索“linux 网卡配置”的&#xff0c;多半不是找不到配置文件&#xff0c;就是改了配置起不来&#xff0c;再要不就是好不容…

作者头像 李华
网站建设 2026/9/8 8:05:59

PyTorch从零实现BERT文本分类:完整指南与踩坑记录

简介&#xff1a;面向 PyTorch 与自然语言处理开发者&#xff0c;这套代码资源完整实现了基于谷歌 BERT 模型的自然语言处理流程&#xff0c;侧重从理论到工程的过渡环节&#xff0c;可帮助读者快速理解双向 Transformer 结构、预训练与微调思想&#xff0c;并顺利跑通文本编码…

作者头像 李华
网站建设 2026/9/8 8:04:45

用Simian精准定位重复代码,在CI中守住代码质量门禁

简介&#xff1a;代码重复检测工具 Simian&#xff08;Similarity Analyser&#xff09;的完整发行包&#xff0c;支持 Java、C#、C、C、JavaScript 等多种语言&#xff0c;面向开发与测试人员&#xff0c;用于在持续集成与代码审查中快速定位重复代码、降低维护成本。压缩包共…

作者头像 李华