惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Help Net Security
Help Net Security
Recent Announcements
Recent Announcements
A
About on SuperTechFans
N
News and Events Feed by Topic
I
Intezer
B
Blog
GbyAI
GbyAI
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Project Zero
Project Zero
T
The Blog of Author Tim Ferriss
Blog — PlanetScale
Blog — PlanetScale
阮一峰的网络日志
阮一峰的网络日志
T
Troy Hunt's Blog
TaoSecurity Blog
TaoSecurity Blog
IT之家
IT之家
MyScale Blog
MyScale Blog
The Register - Security
The Register - Security
Cyberwarzone
Cyberwarzone
Y
Y Combinator Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
酷 壳 – CoolShell
酷 壳 – CoolShell
Stack Overflow Blog
Stack Overflow Blog
P
Privacy & Cybersecurity Law Blog
S
Secure Thoughts
MongoDB | Blog
MongoDB | Blog
N
Netflix TechBlog - Medium
Hacker News - Newest:
Hacker News - Newest: "LLM"
G
Google Developers Blog
罗磊的独立博客
博客园 - 聂微东
G
GRAHAM CLULEY
AWS News Blog
AWS News Blog
Google Online Security Blog
Google Online Security Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
H
Hacker News: Front Page
C
Check Point Blog
L
Lohrmann on Cybersecurity
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Spread Privacy
Spread Privacy
O
OpenAI News
T
Tor Project blog
P
Privacy International News Feed
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Attack and Defense Labs
Attack and Defense Labs
L
LINUX DO - 最新话题
Forbes - Security
Forbes - Security
云风的 BLOG
云风的 BLOG
C
Cisco Blogs
S
Securelist
博客园_首页

博客园 - Earic

背着房贷的程序员,不该继续用“AI替代焦虑”浪费时间 从 Prompt 到 Loop:拆解 AI 工程化四范式的演进逻辑与落地边界 Claude Code 封号与"隐藏标记"争议:一份基于公开资料的核验清单 Go 跨平台真香:我把整个服务端塞进了客户端,用户开箱即用 Agent 直接操作数据库?别急,先看懂这三条路线 用本体论重塑现实:破解大语言模型幻觉的疯狂设想 前端仔接手C#屎山重构:数据库迁移这趟浑水到底有多深? OpenAI Codex 频繁写 SSD 写入问题的真相与应对方案 向量数据库不是银弹:从枚举漏检到 ReACT 多轮召回的实践路径 AI浪潮下的“幸存者”:从焦虑的碎碎念到构建普通人的新核心竞争力 差点被这套AI工具搞离职...搞懂MCP和Skill后,我发现宇宙的尽头是“写小作文” 花 Opus 的钱买到 Sonnet?一行 Python 代码揭穿 API 服务商的“降本增效”骗局 当 rm -rf 发生在物理机节点:从 Virtualizor 漏洞看你的容灾架构为何不堪一击? 深夜惊魂:一行代码让内存爆炸!从 5秒超时到 50ms 响应,我是如何重构 AI 网关的 只有5%的运营人看懂了:从“死积分”到“数字资产”,36期AI分红背后的博弈论 每秒万级Tick的生死时速:技术总监在Golang与Rust间的深夜抉择 拒绝背锅!服务器卡顿CPU却空闲?一文揪出磁盘I/O这个“隐形杀手” 凌晨3点服务器被CPU打爆!从裸奔到铜墙铁壁,这套纵深防御方案救了我的命 【深度解析】SkyWalking 10.2.0版本安全优化与性能提升实战指南 intellij 自动导包 用户中心 - 博客园 用户中心 - 博客园 用户中心 - 博客园 bcrypt 加密 用户中心 - 博客园 用户中心 - 博客园 用户中心 - 博客园 用户中心 - 博客园 用户中心 - 博客园
这才是多数据源的正确打开方式!MyBatis-Plus vs Hibernate 底层原理大揭秘,别再瞎配了
Earic · 2026-01-27 · via 博客园 - Earic

大家好,最近在技术群(QQ群)里,有个小伙伴问了我一个极其经典的问题:“我项目里想做读写分离,是用 Hibernate 的分包配置好,还是 MyBatis-Plus 的动态数据源好?为什么我配置了 JPA 多数据源,事务老是报错?”

这其实触碰到了 Java 后端架构中一个非常痛点的领域:多数据源(Multi-DataSource)的管理

市面上 90% 的教程只教你怎么贴配置,却没告诉你原理。结果就是上线后,要么事务回滚失败,要么切库切了个寂寞。

今天,我们就把 MyBatisMyBatis-PlusHibernate 拉到手术台上,从底层源码的角度,彻底讲透它们的区别以及多数据源的最佳解法。

一、 为什么多数据源这么难搞?

在 Spring 的世界里,单数据源是幸福的。容器启动时,创建一个 DataSource,这就好比你只有一张银行卡,刷卡时不需要动脑子。

但当你需要连接多个数据库(比如:主从库读写分离、分库分表、或者连接老系统的遗留库)时,问题就来了:Spring 怎么知道这一行 SQL 该发给谁?

本质上,多数据源的实现只有两条路:

  1. 静态分包(物理隔离): 你的 UserMapper 永远连库 A,OrderMapper 永远连库 B。
  2. 动态路由(逻辑切换): 同一个 UserMapper,这一次查库 A,下一次查库 B。

二、 核心原理:Spring 的“魔术师”

无论你用 MyBatis 还是 Hibernate,想要实现动态切换,绕不开 Spring 提供的一个核心类:AbstractRoutingDataSource

它的原理其实非常简单,就像一个路由器

  1. 它内部维护了一个 Map<Object, DataSource>,存了所有可用的真实数据源。
  2. 它实现了一个方法 determineCurrentLookupKey()
  3. 关键点: 当数据库连接被请求时,它会先去调用这个方法,通常是从当前线程的 ThreadLocal 中拿到一个 Key(比如 "master" 或 "slave")。
  4. 拿着 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
  • 配置两套 EntityManagerFactoryTransactionManager

虽然配置繁琐(要写两个 Configuration 类),但这是 JPA 下最稳健的方案。

四、 MyBatis-Plus 的“优雅”

相比之下,MyBatis(及其增强版 MyBatis-Plus)就显得非常轻量级听话

MyBatis 执行 SQL 的过程是无状态的,它没有复杂的 Session 缓存包袱。只要在执行 SQL 的那一刻,SqlSessionFactory 拿到了正确的连接,它就能工作。

因此,MyBatis-Plus (MP) 成为了国内处理多数据源的首选。

MP 生态中有一个神器:dynamic-datasource-spring-boot-starter。它的实现原理是 AOP + AbstractRoutingDataSource 的完美结合:

  1. 注解标记: 在方法上加 @DS("slave")
  2. AOP 拦截: 拦截器在方法执行前,把 "slave" 塞进 ThreadLocal
  3. 路由执行: 到底层执行 SQL 时,路由器读取 ThreadLocal,瞬间切换数据源。
  4. 自动清理: 方法结束后,AOP 自动清理 ThreadLocal,防止污染。

五、 原理可视化(点击查看大图)

为了让你更直观地理解,三味画了两张图:

图 1: Spring 动态数据源路由原理 (通用)

image-20260126221332757

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

image-20260126222731834


六、 抄作业:企业级最佳实践代码

如果你在做互联网业务系统,强烈建议使用 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)或者使用多数据源,请务必注意:

  1. 事务的边界: @Transactional 开启时,连接就已经确定了!不要试图在 @Transactional 方法内部再去调用加了 @DS 的方法,不会生效的! 数据源切换必须发生在事务开启之前。
  2. 连接泄露: 以前手动写 ThreadLocal 切换数据源时,最容易忘记 remove(),导致线程复用时带着上一次的脏数据源。使用 MP 的 Starter 可以帮你自动处理这个清理工作。

总结

  • Hibernate/JPA: 适合业务逻辑极其复杂、领域模型驱动(DDD)的项目,多数据源建议用分包隔离
  • MyBatis/MyBatis-Plus: 适合绝大多数互联网业务、需要极致 SQL 掌控的项目,多数据源建议用 dynamic-datasource 动态切换。
  • 原理: 一切魔法的尽头,都是 Spring 的 AbstractRoutingDataSource 和 AOP。

只有懂到底层,才能在报错时从容不迫。

如果你觉得这篇文章让你学到了东西,哪怕是一个知识点,请帮点个赞或者转发给需要的朋友,这对我很重要!