Spring 面经系列 · 第 5 篇事务这条线从 ACID 到失效与多线程面试里问「说一下事务」十个人有九个上来就背 ACID、背四种隔离级别像在答数据库的题。可面试官真正想确认的是Spring 在这条链路里到底干了什么、没干什么。说穿了Spring 自己既不写 redo log也不加行锁它只是把「事务边界」这件事做成了一层统一抽象再靠动态代理 ThreadLocal复用同一个数据库连接把 commit / rollback 翻译给底层。一旦你想让它跨线程、跨库去保证一致性它立刻两手一摊——这不是它管的范围。这篇文章按视频里的顺序把这条线捋一遍ACID 归位 → 管理类型与实现方式 → 七种传播行为 → 隔离级别与数据库的对应 → 声明式事务原理 → 传播行为在源码里怎么落地 → 失效场景与多线程事务。凡是我没有对照源码逐行确认的具体类名、方法名、默认值都会标出来不会替 Spring 打包票。1. ACIDSpring 只兜住「边界」不实现「特性」视频里先过了事务的四大特性并且强调这不是 Spring 独有的——凡是碰数据库、碰事务的地方都得遵守它。这段本身没错但如果面试时只把 ACID 念一遍等于答成了数据库教科书。这里真正要说清的是每一条特性分别由谁负责。原子性A一组操作要么全成功要么全失败。数据库靠 undo log 回滚来兑现「全失败」Spring 只负责把「哪几条语句算一组」这个边界圈出来并在异常时把 rollback 指令发给底层。一致性C视频用的是转账例子——张三和李四加起来一共 3000 块转一万次总额还是 3000。这种「数据前后守恒」本质上是业务规则 数据库约束唯一键、外键、check保证的Spring 并不校验你的业务金额对不对。隔离性I并发事务之间互不干扰防止脏读、不可重复读、幻影读。这是数据库引擎用锁 / MVCC 实现的Spring 只是把隔离级别作为参数透传下去。持久性D一旦提交就永久保存哪怕中途宕机也不能丢。这是数据库的 redo log / WAL 落盘在兜底Spring 完全不管。一张表看清职责边界特性谁真正兑现Spring 负责的那部分原子性 A数据库undo log 回滚定义事务边界 异常时下发 rollback一致性 C业务逻辑 数据库约束不负责只保证「要么都提交要么都回滚」隔离性 I数据库锁 / MVCC把 isolation 参数透传给底层连接持久性 D数据库redo log / WAL不负责只管把 commit 发出去一句话理解Spring 是「事务的编排者」不是「事务的实现者」。它把 commit / rollback / 隔离级别这些动作统一成一套 API底下换成 JDBC、JPA、Hibernate 还是 JTA你写的代码不变但 ACID 这四个字母绝大多数是数据库引擎在替你扛。所以视频里那句「本地事务也好、分布式事务也好都应尽量遵循这四大特性可能做不到百分之百但要尽量遵循」——这句话的落点其实在分布式场景单机里数据库帮你把 A、I、D 兜住了一旦跨库跨服务这些保证就散了才需要你自己想办法。这点在第 7 节的多线程 / 分布式里会回扣。2. 事务管理类型与实现方式两种类型三条实现路径视频把这道题拆成两个小问一是「管理类型」二是「实现方式」。管理类型只有两种编程式和声明式。所谓编程式就是你自己手动控制开启、提交、回滚。最原始的样子就是早期直接用 JDBC拿一个ConnectionsetAutoCommit(false)相当于开启事务操作做完手动commit()出异常就rollback()。Spring 在之上也提供了编程式入口——注入一个TransactionTemplate把数据库操作丢进它的execute()里一旦捕获到异常就把事务状态设成 rollback-only只回滚否则让它正常提交。它的好处是灵活某些声明式满足不了、想手动控制提交时机的场景用得上。声明式就是大家最熟的Transactional不用自己写提交回滚交给 Spring。它又分两种配法——注解或者在 XML 里用tx:advice这种通知的方式声明「哪些方法走什么事务」。维度编程式事务声明式事务边界控制自己写代码控制注解 / XML 元数据声明代表 APITransactionTemplate.executeTransactional/tx:advice侵入性有业务代码里混事务逻辑无靠代理织入灵活度高可随时手动提交受注解语义限制使用频率少数特殊场景绝大多数场景至于实现方式视频说得很直接Spring 声明式事务本身就是基于 AOP 的所以它的实现路径跟 AOP 那套几乎一模一样分基于接口、基于 XML、基于注解三种AspectJ 算第四种但视频强调严格来说只讲三种。基于接口的方式现在不建议用但它对应的是早期的TransactionProxyFactoryBean一类 API——你得自己声明通知、自己创建动态代理。视频作者特意说讲源码之前先普及一遍接口用法反而更容易看懂底层。这里我把它归类为「视频里给的说法」具体类名笔者未对照源码核实。这里补一个视频没展开、但面试常追问的点声明式事务真正落地时靠的是TransactionManager家族。Spring 定义了一个统一的PlatformTransactionManager接口按你底层用什么数据访问技术换不同实现实现类管的是什么典型场景DataSourceTransactionManager一个 JDBCConnection单库、MyBatis / 原生 JDBCJpaTransactionManagerJPA 的EntityManagerSpring Data JPAHibernateTransactionManagerHibernateSession老项目直接用 HibernateJtaTransactionManager应用服务器的全局事务跨多个数据源的分布式事务PlatformTransactionManager这层抽象正是第 1 节「Spring 只当编排者」的技术落点无论哪个实现它做的都是同一件事——开启、提交、回滚、必要时挂起。至于 NESTED 传播、事务挂起这些高级动作并不是每个实现都支持这点第 3、6 节会再提。本表为笔者依据官方文档补充视频未点名这几个类。QA 先摆一个这里容易踩的Q1Spring 是不是自己实现了原子性 / 持久性不是。Spring 拿不到 undo log、也写不了 redo log它做的事是把「一组操作」绑在同一个连接上出异常时补发一条 rollback。真正的「要么全做要么全不做」「提交后不丢」是数据库引擎在磁盘和日志层面完成的。换句话说你把Transactional加在一个不支持事务的存储引擎上比如 MySQL 的 MyISAM 表Spring 发再多 rollback 也没用——因为它根本没有可回滚的东西。前半句是公开事实后半句「MyISAM 失效」是 MySQL 常识笔者未逐条对照文档列为补充。3. 七种传播行为语义、用途与坑全在一张表里视频里先给了一个定义上的提醒面试题目往往只说「说一下事务传播行为」不会帮你把「什么是传播行为」问出来所以你得自己先交代——传播行为只在「两个事务方法嵌套调用」时才有意义也就是外层一个事务方法里又调了内层一个事务方法内层到底是「融入外层」「另开一个」「还是报错」这个规则就是传播行为。Spring 一共定义了七种。视频反复强调前三种REQUIRED / SUPPORTS / REQUIRES_NEW是必须能脱口而出的其余能记多少说多少。我把它做成一张对照表右边两列专门写「典型用途」和「容易踩的坑」传播行为外层有事务时外层无事务时典型用途常见坑REQUIRED默认融入外层共用一个事务自己开一个新事务增删改要保证一起成功失败以为「开了注解就一定是新事务」其实它只是融入SUPPORTS融入外层不开事务裸跑查询方法单独调没必要开事务单独调用时无事务隔离级别设了也白设REQUIRES_NEW挂起外层另开独立新事务自己开一个新事务记日志外层成不成都要求日志落库内外是两个事务外层回滚带不回内层已提交的NOT_SUPPORTED挂起外层以非事务方式跑非事务方式跑不需要事务、且想脱离外层事务的操作别指望它有原子性出异常不会回滚MANDATORY融入外层直接抛异常要求外层必须有事务强制「必须在事务里被调用」的方法单独调用它就炸常被误用NEVER直接抛异常外层不能有事务不开事务正常跑明确禁止在事务上下文里执行的方法一旦它出现在事务链上就报错NESTED在外层里开嵌套事务savepoint相当于 REQUIRED部分回滚子回滚不影响父提交只有特定事务管理器支持默认多为DataSourceTransactionManager视频里对三个高频的解释很实在REQUIRED 适用增删改SUPPORTS 适用查询因为单独调用查询没必要开事务被事务方法调用时才融入REQUIRES_NEW 适用「内外没有业务关联」的场景最典型就是记日志——不管主业务成功失败都想把这条日志留下所以自己另开一个事务不受外层影响。这两个「独立事务」的语义直接引出了源码里的「挂起 / 恢复」机制放到第 6 节讲。Q2NESTED 和 REQUIRES_NEW 都能「自己搞一个」区别在哪区别在它们和外层事务的关系。REQUIRES_NEW是彻底独立的两个事务外层事务被挂起内层自己提交内层回滚不回滚外层、外层回滚也不影响已经提交的内层。NESTED用的是 savepoint保存点内层是外层的一个「子集」——内层回滚只回到 savepoint外层还在但外层一旦回滚内层跟着一起没了。视频里提到 NESTED「比较复杂可以自己控制哪些操作需要事务、哪些不需要」本质就是 savepoint 的部分回滚能力。要注意它的适用范围很窄。七种常量名与语义、NESTED 仅DataSourceTransactionManager开箱支持、REQUIRES_NEW/NOT_SUPPORTED的挂起并非所有管理器都支持——均已对照官方 Propagation 文档核实。视频里「MANDATORY 就是外部一定要有事务」「NEVER 就是我看到外部开事务直接不干了」是口语版对应官方语义一致。4. 隔离级别Spring 的五档 ↔ 数据库的三种并发问题视频对这块的态度很有意思作者说自己不太想讲因为不想把基础概念再念一遍但面试会问所以只做「总结 答题技巧」。隔离性对应的是并发事务下的三类读问题——脏读、不可重复读、幻影读。Spring 的隔离级别本质是对 JDBC 隔离级别的封装一共五档含 DEFAULT。先把三种并发问题用一个「账户转账」的例子讲清楚视频用的就是转账 / 存取款的场景脏读事务二把 1000 改成 800 但还没提交事务一进来查到了 800。读到了未提交的「脏」数据一旦二回滚一拿到的就是错的。不可重复读事务一先读到 1000事务二改成 800并提交了事务一在同一事务里再读变成 800。同一个事务两次读同一行结果不一样。幻影读事务一按条件查总额度读到 3000事务二插入了一条新记录并提交事务一再查总条数 / 总额对不上。它针对的是「整张表里符合条件的行集合」而不是某一行。这里就是最容易搞混的地方视频专门点了一句不可重复读针对「一行数据」被改幻影读针对「整张表」被插入新行。所以解法也不同——不可重复读锁到行级别就够了幻影读得把范围 / 表锁住。隔离级别与三类问题的对应关系Spring 常量脏读不可重复读幻影读数据库大致怎么兜底READ_UNCOMMITTED可能可能可能基本不加读锁并发问题全开READ_COMMITTED阻止可能可能只读已提交数据解决脏读REPEATABLE_READ阻止阻止可能视频说核心是「加锁」锁住读过的一行SERIALIZABLE阻止阻止阻止视频直白说「说白了就是表锁」DEFAULT取决于数据库——用底层数据库自己的默认级别关键一条Spring 的隔离级别默认是DEFAULT也就是跟随数据库。视频给了两个具体值——MySQL 默认是「可重复读」Oracle 默认是「读已提交」。这属于数据库公开事实也是面试最爱顺带问的一句。Q3给一个 SUPPORTS 的方法设 isolation会生效吗不会。这是我在官方文档里核到的一条硬性约束隔离级别只对「新开的事务」有意义。SUPPORTS单独调用时根本不开事务NOT_SUPPORTED更是以非事务方式跑你给它设READ_COMMITTED也是白设。视频没点破这条但和第 3 节 SUPPORTS 单独调时无事务的说法是自洽的。此条已对照Transactional官方文档核实isolation 仅适用于新开事务实际只对 REQUIRED / REQUIRES_NEW 这类会创建事务的行为有意义。补充一点边界视频用的是标准 SQL 模型幻影读只有 SERIALIZABLE 挡得住而 MySQL InnoDB 在其默认的 REPEATABLE_READ 下靠 next-key / gap lock 其实也能挡大部分幻影读。这属于 MySQL 引擎的实现细节笔者未对照 MySQL 文档逐条核实仅作提示面试时按「标准模型」答最稳。5. 声明式事务原理动态代理 ThreadLocal 复用同一条连接这是全套题里难度拉满的一块。视频把它归到「你得看过源码才答得出」的档次但主线其实只有一句话Spring 声明式事务整套机制就是套在 AOP 上跑的而事务的本质是「同一线程复用同一条数据库连接」。先看怎么用Configuration配置类上要先加EnableTransactionManagement才能在各方法上用Transactional。视频强调——不加这个开关注解是不生效的。原理分三大步和 AOP 完全对齐解析 → 创建动态代理 → 调用增强。第一步解析。AOP 里解析的是 advisor切面 pointcut 切点 advice 通知发生在 Bean 创建前的第一个后置处理器里。但事务「没有切面、也没有前后置通知」它解析的是什么视频给的答案是EnableTransactionManagement会导入一个配置类这个配置类帮我们注册了一个针对事务的 advisor。所以事务的 advisor 不是你用Around之类注解写出来的而是它内置提供的。这个 advisor 也有 pointcut只不过切点不是切点表达式而是「你方法 / 类 / 接口上有没有Transactional」。第二步创建代理。在 Bean 初始化后、由 Bean 后置处理器创建动态代理规则和 AOP 一样有接口走 JDK 动态代理没接口走 CGLIB。创建前拿 advisor 的 pointcut 去匹配Transactional匹配方式很宽——方法上有、类上有、接口 / 抽象类 / 父类上有满足任意一个就给你建代理。第三步调用增强这才是事务真正发生的地方。代理把目标方法包了一层 try / catch进来先在增强里拿一个Connection、把它setAutoCommit(false)开启事务执行目标方法里的 SQL正常结束就 commit抛异常就 rollback。视频那句「说白了它就是在动态代理的增强类里为你包了一层事务」非常精准。调用 Transactional 方法 → 代理拦截取连接 setAutoCommit(false) 开事务 → 把 Connection 存进 ThreadLocal以 DataSource 为 key → try { 执行目标方法 / SQL } → 正常 commit异常 rollback为什么是 ThreadLocal视频里 P03 和 P38 反复落到这个点外层方法把连接存进ThreadLocal就是为了嵌套调用的内层方法能从同一个ThreadLocal里取到同一个Connection——只有用同一条连接才能保证是同一个事务。同时内层如果发现ThreadLocal里已经有值就不会自己去提交而是把提交权统一交给最外层。这套「按线程绑定资源」的机制官方实现就是TransactionSynchronizationManager下面第 6 节源码会点名它。这一层我核到了官方描述TransactionSynchronizationManager以「每线程per-thread」方式把资源绑到当前线程绑定用的 key 通常就是DataSourcevalue 是连接持有对象同线程同事务里后续再取拿到的就是同一个连接事务结束时由事务管理器clear掉、解绑。这与视频讲的「存进 ThreadLocal、以 DataSource 为 key、取出复用」完全对得上。6. 传播行为的源码实现挂起、恢复、新建事务的分支第 5 节讲清楚了「融入」但视频明确指出传播行为是在「调用动态代理」那一刻处理的跟解析切面、创建代理没关系——所以本节只聚焦调用阶段。源码虽然复杂但七种传播归纳起来就两种走向要么融入外部事务要么新建一个事务。视频就讲这两种。要讲清这两个走向先记住两个贯穿始终的关键点视频反复强调「看过源码就记这两个」开启事务后会返回一个事务状态对象源码里叫TransactionInfo视频原话里面有个关键布尔属性newTransaction——它是不是一个新事务。外层自己创建连接开事务时它为true内层只是融入时它是false。提交动作永远只提交「当前ThreadLocal里那条连接」。所以谁把连接放进 / 拿出ThreadLocal谁就决定了这次事务到底提不提交。走向 A内嵌融入外层。外层创建Connection、setAutoCommit(false)、存入ThreadLocal、newTransactiontrue然后执行目标方法目标方法里调了内嵌事务方法又走一遍内嵌的动态代理增强。内嵌增强里先判断ThreadLocal上是不是已经有连接——有说明外面已经有事务了再判断当前传播行为是「融入」这一类于是不创建新连接newTransactionfalse直接用外层连接执行 SQL。内嵌方法执行完到提交这一步会判newTransaction是false→不提交方法结束、控制权回到外层。外层继续往下走最后判newTransactiontrue从ThreadLocal拿连接提交。整个过程就是一次提交落在最外层。走向 B内嵌新建事务对应 REQUIRES_NEW / NOT_SUPPORTED 的「挂起」。前面几步一样外层建连接、开事务、newTransactiontrue、目标方法调用内嵌。区别在内嵌判断传播行为是「新建事务」这一类时它做了一件关键的事把外层事务的相关信息连接、隔离级别、是否只读等先「暂存」起来也就是把外层ThreadLocal里存的事务信息置空然后自己新建一条Connection、放进ThreadLocal——因为第 5 节说过一个线程的ThreadLocal只保证有一条连接 / 一个事务。这个「暂存 置空 自建」的动作就是官方所谓的挂起suspend。内嵌newTransactiontrue执行自己的 SQL到提交时判为 true →提交自己提交完它再把「之前暂存的外层事务信息」回归回ThreadLocal——这就是恢复resume让外层重新占回当前线程。控制权回到外层后外层同样判newTransactiontrue拿回自己的连接提交。融入 外层存连接 → 内嵌查到已有连接(newTransactionfalse) → 不提交 → 外层统一提交 新建/挂起外层存连接 → 内嵌「暂存外层信息 置空 自建新连接」→ 内嵌提交自己 → 「恢复外层信息」→ 外层再提交视频也坦诚这块「没看过源码听着会晕」。所以面试时不用背全抓住三句话就能立住——连接存ThreadLocal、提交只提交ThreadLocal里的连接、newTransaction决定这次提不提交新建事务比融入多了个「暂存 / 回归挂起 / 恢复」的概念。至于「外部存在事务却报异常」那类NEVER / MANDATORY视频直接说不是重点略过。这里回扣第 3 节那句「挂起不是所有管理器都支持」官方 Propagation 文档明确写了REQUIRES_NEW/NOT_SUPPORTED的事务挂起在部分管理器上开箱不支持尤其JtaTransactionManager要看具体是否配置了相应的TransactionSynchronization。也就是说上面这套「暂存 / 置空 / 恢复」的漂亮逻辑落到JtaTransactionManager上不一定走得通。7. 失效场景与多线程事务Spring 兜不住的那些坑前面全是「怎么生效」这一节讲「什么时候不生效」这也是最容易在面试里被追问、被要求「说一个你踩过的坑」的地方。视频作者给答题定调把失效原因答全说明你真在项目里踩过。失效场景清单。视频把 P40 和 AOP 的失效思路绑定因为事务本来就基于 AOP所以凡是 AOP 失效的原因事务同样失效。我做成了可勾选的清单并补上了视频没明说、但面试常凑齐的几条失效场景根因解决办法方法是private/ 非 public代理增强默认拦不到非公开方法事务织不进去改成public目标类没交给 Spring 管理不是 Bean根本没为其创建代理加上Service/Component等同类自调用内部调用没重新走代理增强不触发传播行为也失效视频列为最关键注入自身走代理或开exposeProxy用AopContext.currentProxy()异常被自己 catch 后没抛出代理增强没感知到异常不会触发 rollback别吞异常或手动setRollbackOnly抛的是 checked 异常默认只对RuntimeException和Error回滚checked 异常不回滚Transactional(rollbackFor Exception.class)Transactional标在接口方法上、却用 CGLIB 代理CGLIB 基于类接口上的注解匹配不到注解标到实现类 / 实现方法上传播行为「吞掉」了预期事务如误用 SUPPORTS/NEVER/NOT_SUPPORTED以为开了其实没开核对第 3 节表格语义存储引擎不支持事务底层无可回滚的东西如 MyISAM 表换成支持事务的引擎InnoDB多线程执行事务信息绑在ThreadLocal子线程拿不到下详见第 7 节后半 / 分布式方案自调用这一条值得多说两句因为它是「内部调用导致传播失效」的根源。视频的例子是本类事务方法 A 里直接调本类的 B而你把 B 的传播行为设成「重新开一个」REQUIRES_NEW结果 B 根本没另开事务。原因不是传播规则坏了而是你走的是this.B()这种本类直接调用没经过代理对象那段「新建事务 / 挂起」的增强代码压根没执行。两种解法视频都给了一是把自身再注入一遍Resource private XxxService self;用self.B()走代理二是在配置里启用 AOP 并「暴露代理到当前线程」调用时通过AopContext.currentProxy()拿代理对象再调。exposeProxy/AopContext的精确 API 名笔者未逐字对照源码列为视频原话。再看 checked 异常这条——它是被问得最多、又最反直觉的默认情况下你try里抛了个 checked 异常事务不回滚。官方文档给的回滚默认规则很明确不配自定义规则时只对RuntimeException和Error回滚checked 异常不回滚想让 checked 也回滚用rollbackFor或rollbackForClassName。所以视频说「把异常抛出去」还不够抛对了类型 配对了rollbackFor才行。Q4Spring 用多线程跑事务能保证一致性吗不能——视频里这题是作者一个学员去面试某大厂时碰到的原题答案斩钉截铁保证不了一致性也保证不了原子性。原理就是第 5 节那句话事务信息全在ThreadLocal里一个线程只绑一条连接、只有一个事务。你在 A 里new Thread调 BB 在自己的线程上查不到 A 那个ThreadLocal里的连接于是它自己另开一条连接、另起一个事务两人各干各的。结果就是 B 抛异常只回滚 BA 的数据照样提交——A 插进去了、B 回滚了数据库里只剩一半。官方也写得很直白Transactional不会传播到方法内部新起的线程。视频作者给的出路有两条P39一是自己用编程式事务手动控制提交回滚二是走分布式事务的思路用二阶段提交那套。这两条都指向同一个事实多线程一致性不在 Spring 声明式事务的能力范围内。「2 句代码」的解法P03——以及它到底没解决什么。P03 给了一个看起来像银弹的技巧作者说「只要两句代码就能解决多线程事务失效」。它的本质是把外层事务的连接手动搬进子线程的ThreadLocalResourceprivateDataSourcedataSource;TransactionalpublicvoidmethodA()throwsInterruptedException{// 第 1 句取出外层事务已绑定到本线程的连接持有对象ObjectholderTransactionSynchronizationManager.getResource(dataSource);ThreadtnewThread(()-{// 第 2 句把它手动绑进子线程自己的 ThreadLocalTransactionSynchronizationManager.bindResource(dataSource,holder);methodB();// 走 B 的代理读到同一个 connection});t.start();t.join();// 关键父线程必须等子线程跑完再统一提交}视频里的演示效果确实对了B 抛异常后A 的那条也不提交了数据库刷新成一致要么都有要么都无。它「解决」的是——让子线程复用外层那一条Connection于是内外变成同一个事务提交权统一收回到最外层。但这里必须把话说透它没解决的部分恰恰才是重点它没有做多线程并行事务。你复用的是同一条 JDBC 连接而一条Connection不是线程安全的。要让 A 线程和 B 线程同时往同一条连接上写 SQL结果是不可预料的。所以你看代码里那句t.join()——父线程必须串行等待子线程跑完再提交。说白了这不是「并发事务」而是「把一个本该串行的活儿挪到别的线程去跑再拼回同一个连接」。子线程一多、或者两个线程真并发跑方案立刻崩。它只管「事务边界」不管分布式一致性。库换了、连接换了、跨服务了这套bindResource就搬不过去了。视频作者自己也留了一句「解决方案其实还有很多种」言下之意这两句是入门技巧不是终局。它绕过了 Spring 的正常资源管理。手动bindResource若不配对unbindResource线程复用时比如线程池连接会残留、脏读上一个任务的上下文。生产环境里至少要补三件事子线程用完解绑、保证串行、评估是否根本不该用多线程。一句话理解这两句代码不是「让 Spring 支持了多线程事务」而是「你自己手动把子线程塞进外层那条连接的上下文里」——它把并发问题偷偷变成了串行。真正的多线程 / 跨服务一致性还得回到「编程式手动编排」或「分布式事务二阶段提交 / Seata 那一类」上去解。最后总结ACID 归位A / I / D 主要靠数据库引擎Spring 只做「事务边界的编排者」负责圈范围、发 commit / rollback、透传隔离级别。两种管理类型编程式TransactionTemplate/ 手动 JDBC与声明式注解 / XML实现上分接口 / XML / 注解三条路底层统一走PlatformTransactionManager家族。七种传播行为REQUIRED / SUPPORTS / REQUIRES_NEW 是必背的三件套REQUIRES_NEW 是「独立事务」NESTED 是「savepoint 部分回滚」MANDATORY / NEVER 是「条件不满足就抛异常」。隔离级别五档跟数据库走DEFAULT跟随数据库MySQL 默认 RR、Oracle 默认 RC且隔离级别只对新开事务有效。原理主线AOP 代理 ThreadLocal持连接事务本质 同一线程复用同一条连接TransactionSynchronizationManager是这套绑定的官方实现。传播的源码记住newTransaction决定提不提交、提交只提交ThreadLocal里的连接新建事务比融入多了「挂起 / 恢复」。失效与多线程失效原因基本 AOP 失效原因非 public、自调用、吞异常、checked 异常、接口注解遇 CGLIB、Bean 没管理、引擎不支持多线程 Spring 保证不了一致性「2 句代码」是把并发偷偷掰成串行复用同一连接的技巧不是银弹。我个人在面试里最看重的一条判断标准是候选人能不能把「事务 同一线程复用同一连接」这句因果链讲顺。能讲顺的人传播行为、失效场景、多线程问题基本都不会答错讲不顺的背再多七种传播也经不起追问。事实说明视频里给的事实ACID 四条与转账 / 存取款例子管理类型 编程式 声明式、实现方式 接口 / XML / 注解七种传播行为语义及 REQUIRED / SUPPORTS / REQUIRES_NEW 三件套用途三种并发读问题与「不可重复读针对行、幻影读针对表」的区分、MySQL 默认 RR / Oracle 默认 RC、SERIALIZABLE「就是表锁」、REPEATABLE_READ「核心是加锁」声明式事务 套 AOP 三步解析 advisor、创建动态代理、调用增强、EnableTransactionManagement导入配置类注册事务 advisor、连接存ThreadLocal以 DataSource 为 key传播行为在调用动态代理时处理、TransactionInfo.newTransaction决定是否提交、新建事务的「暂存 / 置空 / 恢复」失效原因非 public、类未交 Spring 管理、吞异常、自调用走 this 不过代理、接口注解遇 CGLIB、rollbackFor未配、多线程P39 结论 多线程保证不了一致性、可用编程式或二阶段提交P03 的getResourcebindResource两句代码演示与效果。官方文档核实过的事实Spring Framework 7.0.9 javadoc / referencePropagation 七枚举常量语义与 REQUIRED 为默认、NESTED 仅DataSourceTransactionManager开箱支持、REQUIRES_NEW/NOT_SUPPORTED挂起并非所有管理器支持Isolation 五常量语义与 DEFAULT 跟随数据库默认回滚只对RuntimeExceptionError、checked 不回滚、可用rollbackFor/noRollbackFor覆盖isolation 仅对新开事务有效Transactional不传播到方法内新建的线程自调用绕过代理不触发事务TransactionSynchronizationManager按线程绑定资源、key DataSource、getResource/bindResource/unbindResourceSpring 提供跨 JDBC / JPA / Hibernate / JTA 的一致抽象。笔者判断 / 未核实TransactionTemplate、TransactionProxyFactoryBean、exposeProxy/AopContext.currentProxy()的精确类名方法名为视频原话、笔者未逐字对照源码「MyISAM 不支持事务导致失效」是 MySQL 常识、未对照 MySQL 文档「InnoDB 在 RR 下靠 gap lock 挡幻影读」是引擎实现细节、未核实仅作提示PlatformTransactionManager实现类家族表为笔者依官方文档补充、视频未点名P03「2 句代码」代码里的t.join()、finally解绑、以及对「Connection 非线程安全 / 实为串行」的分析为笔者补充判断视频演示未展开。参考资料 致谢[1] Spring面经-哔哩哔哩本系列素材来源分 PP03 / P33 / P34 / P35 / P36 / P37 / P38 / P39 / P40[2] Spring Framework 官方文档 · Transactions[3] Spring Propagation 枚举 javadoc[4] Spring Isolation 枚举 javadoc[5] Spring Transactional javadoc[6] Spring TransactionSynchronizationManager javadoc[7] Spring 项目主页[8] MySQL 官网 ・ Oracle Database 官网 ・ Hibernate 官网 ・ Seata 官网本文覆盖分 P分 P 编号视频原始标题本文对应小节P33事务四大特性第 1 节 ACIDP34Spring 支持的事务管理类型和实现方式第 2 节 管理类型与实现方式P35说一下 Spring 的事务传播行为第 3 节 七种传播行为P36说一下 spring 的事务隔离第 4 节 隔离级别P37Spring 事务实现基本原理第 5 节 声明式事务原理P38Spring 事务传播行为实现原理第 6 节 传播行为源码实现P40Spring 事务的失效原因第 7 节 失效场景清单P39Spring 多线程事务能否保证事务的一致性第 7 节 Q4 多线程一致性P032 句代码即可解决 Spring 多线程事务失效问题第 7 节 「2 句代码」解法
阅读完成 · 觉得有帮助?