核心内容摘要
华为nova15简直细节狂魔!潮玩美学+桌面新玩法,太突出了~
这其实触碰到了 Java 后端架构中一个非常痛点的领域多数据源Multi-DataSource的管理。
市面上 90% 的教程只教你怎么贴配置却没告诉你原理。
结果就是上线后要么事务回滚失败要么切库切了个寂寞。
今天我们就把MyBatis、MyBatis-Plus和Hibernate拉到手术台上从底层源码的角度彻底讲透它们的区别以及多数据源的最佳解法。
为什么多数据源这么难搞在 Spring 的世界里单数据源是幸福的。
容器启动时创建一个DataSource这就好比你只有一张银行卡刷卡时不需要动脑子。
但当你需要连接多个数据库比如主从库读写分离、分库分表、或者连接老系统的遗留库时问题就来了Spring 怎么知道这一行 SQL 该发给谁本质上多数据源的实现只有两条路静态分包物理隔离你的UserMapper永远连库 AOrderMapper永远连库 B。
动态路由逻辑切换同一个UserMapper这一次查库 A下一次查库 B。
核心原理Spring 的“魔术师”无论你用 MyBatis 还是 Hibernate想要实现动态切换绕不开 Spring 提供的一个核心类AbstractRoutingDataSource。
它的原理其实非常简单就像一个路由器它内部维护了一个MapObject, DataSource存了所有可用的真实数据源。
它实现了一个方法determineCurrentLookupKey()。
关键点当数据库连接被请求时它会先去调用这个方法通常是从当前线程的ThreadLocal中拿到一个 Key比如 “master” 或 “slave”。
拿着 Key 去 Map 里找找到谁就用谁。
听起来很简单别急坑在后面。
不同的 ORM 框架对这个“路由器”的兼容性天差地别。
Hibernate (JPA) 的“倔强”很多用 JPA 的同学尝试用AbstractRoutingDataSource做动态切换往往会撞得头破血流。
为什么因为 Hibernate 太“智能”了。
Hibernate 有一级缓存Session和复杂的上下文管理。
在一个事务Transaction开启的瞬间Hibernate 就需要确定EntityManager并绑定一个数据库连接。
一旦绑定在这个事务结束前它通常不会轻易释放或切换连接。
这就导致了一个经典 Bug你在 Service 层加了注解想切库但因为事务早已开启Hibernate 根本不理会你的ThreadLocal变动依然倔强地用着主库的连接。
Hibernate 的最佳解法分包隔离Split Packages既然动态切换容易出事那就物理隔离。
将所有连接 A 库的 Repository 放在com.app.repo.primary。
将所有连接 B 库的 Repository 放在com.app.repo.secondary。
配置两套EntityManagerFactory和TransactionManager。
虽然配置繁琐要写两个 Configuration 类但这是 JPA 下最稳健的方案。
MyBatis-Plus 的“优雅”相比之下MyBatis及其增强版 MyBatis-Plus就显得非常轻量级和听话。
MyBatis 执行 SQL 的过程是无状态的它没有复杂的 Session 缓存包袱。
只要在执行 SQL 的那一刻SqlSessionFactory拿到了正确的连接它就能工作。
因此MyBatis-Plus (MP)成为了国内处理多数据源的首选。
MP 生态中有一个神器dynamic-datasource-spring-boot-starter。
它的
实现原理是AOP AbstractRoutingDataSource的完美结合注解标记在方法上加DS(slave)。
AOP 拦截拦截器在方法执行前把 “slave” 塞进ThreadLocal。
路由执行到底层执行 SQL 时路由器读取ThreadLocal瞬间切换数据源。
自动清理方法结束后AOP 自动清理ThreadLocal防止污染。
原理可视化点击查看大图为了让你更直观地理解三味画了两张图图 1: Spring 动态数据源路由原理 (通用)图 2: MyBatis-Plus vs Hibernate 多数据源策略对比
抄作业企业级最佳实践代码如果你在做互联网业务系统强烈建议使用 MyBatis-Plus dynamic-datasource的组合。
这是目前最优雅、代码侵入性最小的方案。
引入依赖 (pom.xml)dependency groupIdcom.baomidou/groupId artifactIddynamic-datasource-spring-boot-starter/artifactId version
3.
1/version /dependency
配置文件 (application.yml)看多么清爽spring: datasource: dynamic: primary: master # 默认走主库 strict: false # 严格模式匹配不到数据源是否报错 datasource: master: url: jdbc:mysql://
192.
168.
100:3306/main_db username: root slave_1: url: jdbc:mysql://
192.
168.
101:3306/main_db username: root
业务代码 (Service)一个注解搞定切换谁用谁知道。
Service public class OrderService { Autowired private OrderMapper orderMapper; // 默认走 master用于写入 public void createOrder(Order order) { orderMapper.insert(order); } // 显式声明走从库用于查询 DS(slave_
public Order getOrderInfo(Long id) { return orderMapper.selectById(id); } }
避坑指南重点如果你一定要在项目中混用JPA MP或者使用多数据源请务必注意事务的边界Transactional开启时连接就已经确定了不要试图在Transactional方法内部再去调用加了DS的方法不会生效的数据源切换必须发生在事务开启之前。
连接泄露以前手动写ThreadLocal切换数据源时最容易忘记remove()导致线程复用时带着上一次的脏数据源。
使用 MP 的 Starter 可以帮你自动处理这个清理工作。
总结Hibernate/JPA适合业务逻辑极其复杂、领域模型驱动DDD的项目多数据源建议用分包隔离。
MyBatis/MyBatis-Plus适合绝大多数互联网业务、需要极致 SQL 掌控的项目多数据源建议用dynamic-datasource动态切换。
原理一切魔法的尽头都是 Spring 的AbstractRoutingDataSource和 AOP。
只有懂到底层才能在报错时从容不迫。
如果你觉得这篇文章让你学到了东西哪怕是一个知识点请帮点个赞或者转发给需要的朋友这对我很重要