






















大家好,最近在技术群(QQ群)里,有个小伙伴问了我一个极其经典的问题:“我项目里想做读写分离,是用 Hibernate 的分包配置好,还是 MyBatis-Plus 的动态数据源好?为什么我配置了 JPA 多数据源,事务老是报错?”
这其实触碰到了 Java 后端架构中一个非常痛点的领域:多数据源(Multi-DataSource)的管理。
市面上 90% 的教程只教你怎么贴配置,却没告诉你原理。结果就是上线后,要么事务回滚失败,要么切库切了个寂寞。
今天,我们就把 MyBatis、MyBatis-Plus 和 Hibernate 拉到手术台上,从底层源码的角度,彻底讲透它们的区别以及多数据源的最佳解法。
在 Spring 的世界里,单数据源是幸福的。容器启动时,创建一个 DataSource,这就好比你只有一张银行卡,刷卡时不需要动脑子。
但当你需要连接多个数据库(比如:主从库读写分离、分库分表、或者连接老系统的遗留库)时,问题就来了:Spring 怎么知道这一行 SQL 该发给谁?
本质上,多数据源的实现只有两条路:
UserMapper 永远连库 A,OrderMapper 永远连库 B。UserMapper,这一次查库 A,下一次查库 B。无论你用 MyBatis 还是 Hibernate,想要实现动态切换,绕不开 Spring 提供的一个核心类:AbstractRoutingDataSource。
它的原理其实非常简单,就像一个路由器:
Map<Object, DataSource>,存了所有可用的真实数据源。determineCurrentLookupKey()。ThreadLocal 中拿到一个 Key(比如 "master" 或 "slave")。听起来很简单?别急,坑在后面。 不同的 ORM 框架对这个“路由器”的兼容性天差地别。
很多用 JPA 的同学尝试用 AbstractRoutingDataSource 做动态切换,往往会撞得头破血流。
为什么?因为 Hibernate 太“智能”了。
Hibernate 有一级缓存(Session)和复杂的上下文管理。在一个事务(Transaction)开启的瞬间,Hibernate 就需要确定 EntityManager,并绑定一个数据库连接。一旦绑定,在这个事务结束前,它通常不会轻易释放或切换连接。
这就导致了一个经典 Bug:你在 Service 层加了注解想切库,但因为事务早已开启,Hibernate 根本不理会你的 ThreadLocal 变动,依然倔强地用着主库的连接。
Hibernate 的最佳解法:分包隔离(Split Packages)
既然动态切换容易出事,那就物理隔离。
com.app.repo.primary。com.app.repo.secondary。EntityManagerFactory 和 TransactionManager。虽然配置繁琐(要写两个 Configuration 类),但这是 JPA 下最稳健的方案。
相比之下,MyBatis(及其增强版 MyBatis-Plus)就显得非常轻量级和听话。
MyBatis 执行 SQL 的过程是无状态的,它没有复杂的 Session 缓存包袱。只要在执行 SQL 的那一刻,SqlSessionFactory 拿到了正确的连接,它就能工作。
因此,MyBatis-Plus (MP) 成为了国内处理多数据源的首选。
MP 生态中有一个神器:dynamic-datasource-spring-boot-starter。它的实现原理是 AOP + AbstractRoutingDataSource 的完美结合:
@DS("slave")。ThreadLocal。ThreadLocal,瞬间切换数据源。ThreadLocal,防止污染。为了让你更直观地理解,三味画了两张图:
图 1: Spring 动态数据源路由原理 (通用)

图 2: MyBatis-Plus vs Hibernate 多数据源策略对比

如果你在做互联网业务系统,强烈建议使用 MyBatis-Plus + dynamic-datasource 的组合。这是目前最优雅、代码侵入性最小的方案。
1. 引入依赖 (pom.xml)
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.6.1</version>
</dependency>
2. 配置文件 (application.yml)
看,多么清爽!
spring:
datasource:
dynamic:
primary: master # 默认走主库
strict: false # 严格模式,匹配不到数据源是否报错
datasource:
master:
url: jdbc:mysql://192.168.1.100:3306/main_db
username: root
slave_1:
url: jdbc:mysql://192.168.1.101:3306/main_db
username: root
3. 业务代码 (Service)
一个注解搞定切换,谁用谁知道。
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
// 默认走 master,用于写入
public void createOrder(Order order) {
orderMapper.insert(order);
}
// 显式声明走从库,用于查询
@DS("slave_1")
public Order getOrderInfo(Long id) {
return orderMapper.selectById(id);
}
}
如果你一定要在项目中混用(JPA + MP)或者使用多数据源,请务必注意:
@Transactional 开启时,连接就已经确定了!不要试图在 @Transactional 方法内部再去调用加了 @DS 的方法,不会生效的! 数据源切换必须发生在事务开启之前。ThreadLocal 切换数据源时,最容易忘记 remove(),导致线程复用时带着上一次的脏数据源。使用 MP 的 Starter 可以帮你自动处理这个清理工作。AbstractRoutingDataSource 和 AOP。只有懂到底层,才能在报错时从容不迫。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。