面试Java后端,Spring事务几乎是必问题。可很多人背了传播行为、隔离级别,一到实战就懵。其实,Spring事务的核心就一张图:一个代理,两个核心,三大陷阱。搞懂这张图,面试和开发都稳了。
一张图:代理 + 事务管理器 + 连接
想象一下:你的Service方法被一个“代理对象”包裹。调用方法时,代理先向PlatformTransactionManager申请事务,拿到一个数据库连接,开启事务;然后执行业务代码;成功则提交,异常则回滚;最后归还连接。这张图的关键是:事务的边界在代理层,不在方法内部。所以,方法内部直接调用另一个@Transactional方法,代理根本不知道,事务自然失效——这就是自调用陷阱。
两个核心:传播行为与隔离级别
传播行为解决“多个事务方法互相调用时,事务怎么处理”。面试最爱问REQUIRED和REQUIRES_NEW。REQUIRED是默认:有事务就加入,没有就新建。REQUIRES_NEW是挂起当前事务,另起一个新事务,新事务提交或回滚不影响外层。还有NESTED,基于保存点,外层回滚会带走内层,内层回滚不影响外层。记住:REQUIRES_NEW会开两个连接,容易死锁,慎用。
隔离级别对应数据库的四种:读未提交、读已提交、可重复读、串行化。Spring默认用数据库的默认级别,MySQL是REPEATABLE READ。面试常问:@Transactional(isolation = Isolation.READ_COMMITTED)能解决幻读吗?答案是看数据库实现,MySQL的RR通过MVCC+间隙锁解决了大部分幻读,但RC不行。
三大陷阱:失效场景必须背熟
第一,自调用失效。同类中A方法调B方法,B有@Transactional,但A没走代理,B的事务不生效。解法:注入自己、用AopContext.currentProxy()、或拆到另一个Service。
第二,异常类型不对。Spring默认只回滚RuntimeException和Error。如果抛出Checked Exception(如IOException),事务不回滚。解法:@Transactional(rollbackFor = Exception.class)。
第三,方法非public。Spring AOP基于代理,private、protected、包级方法上的@Transactional不生效。解法:改成public。
还有:多线程调用,事务绑定在ThreadLocal上,子线程拿不到;数据库引擎不支持,MyISAM没有事务;事务未开启,比如没配@EnableTransactionManagement。
一张图延伸:事务同步与传播
再深入一点,Spring事务还有TransactionSynchronization,可以在提交前后做钩子,比如发消息、清缓存。但注意:事务提交前发MQ,如果提交失败,消息已发出,数据不一致。解法:用事务消息或本地消息表。
面试怎么答?
被问到Spring事务,先画那张图:代理→事务管理器→连接。然后说传播行为,重点讲REQUIRED和REQUIRES_NEW的区别。接着讲失效场景,至少说出自调用、异常类型、非public三个。最后补一句:实际开发中,我倾向在Service层加事务,避免大事务,必要时用编程式事务TransactionTemplate更可控。
总结
Spring事务不复杂,一张图就能串起来:代理控制边界,传播决定嵌套,隔离控制并发,陷阱决定成败。面试时别只背概念,结合场景讲,比如“下单扣库存,用REQUIRED保证一起回滚;发短信通知,用REQUIRES_NEW避免短信失败导致订单回滚”。这样答,面试官会觉得你真用过。记住:事务是手段,数据一致才是目的。