连载中 8/18

事务(上):@Transactional 的实现原理

2026-11-06 · 44 阅读 · 0 评论 · 0 赞

一个注解的幕后班底

@Transactional 看着像魔法,拆开是三个角色的流水线协作:代理——AOP 在方法前后织入事务逻辑,TransactionInterceptor 就是那个通知;事务管理器——PlatformTransactionManager,真正执行 begin/commit/rollback 的角色;ThreadLocal——把连接绑定到当前线程,让一整段业务里的所有 SQL 共用同一条连接。三者缺一,事务就转不起来。

一个事务方法的完整旅程

外部调用 → 代理拦截
  1 事务管理器拿连接,setAutoCommit(false)    开启事务
  2 连接绑到 ThreadLocal                      本线程后续取到的都是它
  3 执行业务方法                               MyBatis/JDBC 从 ThreadLocal 拿同一连接
  4a 无异常       → commit
  4b 异常命中规则 → rollback
  5 解绑连接、恢复自动提交、清理现场

ThreadLocal:同一连接的秘密

为什么一个方法里的三次 insert 天然在一个事务里?因为它们用的是同一条数据库连接。MyBatis 执行 SQL 时通过 DataSourceUtils.getConnection 取连接——这个工具类先查 TransactionSynchronizationManager 的 ThreadLocal:事务开着,返回绑定的那条;没开,才向池里新拿。由此得出硬前提:@Transactional 是同线程模型,跨线程、异步任务,事务上下文带不过去——这也是失效场景之一。

默认回滚规则:最大的坑

@Transactional 默认只回滚 RuntimeException 和 Error——受检异常(IOException、自定义 checked 异常)抛出来默认照常提交。业务里自定义异常若是受检类型,事务等于没加。标准姿势:@Transactional(rollbackFor = Exception.class) 一把梭掉所有异常。这规则反直觉但有历史原因:受检异常当年被设想为「可预期的业务情况」,不该一律回滚——现实是没人记得这个假设,统一显式配置最稳。

事务管理器

JDBC/MyBatis 用 DataSourceTransactionManager(commit 就是 connection.commit);JPA 用 JpaTransactionManager。多数据源应用里每个数据源要有各自的事务管理器,@Transactional(transactionManager = "xxx") 指定用哪个——管理器配错,注解形同虚设。

原理在手,失效场景不用背。下一篇把八种失效姿势从机制逐一推出来——每一种都能指着原理说清「它为什么失效」。

☕
503

10 年全栈工程师 · 503咖啡馆主理人

#Spring#事务#Transactional#ThreadLocal#Spring Boot

评论 (0)

热门推荐

连载中 11/22

主从搭建实操:从零配出一主两从

光讲原理不过瘾?手把手搭一主两从:my.cnf 六个参数、复制账号、GTID、CHANGE REPLICATION SOURCE TO、SHOW REPLICA STATUS 验收,附翻车排查清单。

#MySQL#主从复制#GTID#主从搭建#高可用
2026-05-07 · 10136 阅读 · 0 评论 · 0 赞
连载中 16/22

连接池:HikariCP 参数与连接风暴

连接池不是越大越好:8 核机器配 1000 连接反而更慢的数学原理,HikariCP 四个必调参数,maxLifetime 与 wait_timeout 的隐形陷阱。

#MySQL#连接池#HikariCP#maxLifetime#连接风暴
2026-05-10 · 9900 阅读 · 0 评论 · 0 赞
连载中 4/16

缓存穿透:恶意 ID 打穿 MySQL 的四道防线

请求的数据在缓存和数据库里都不存在时,缓存形同虚设。聊聊参数校验、空值缓存、布隆过滤器、限流熔断四道防线的原理与组合打法。

#Redis#缓存穿透#布隆过滤器#高可用
2026-05-16 · 9331 阅读 · 21 评论 · 287 赞