1. Spring事务失效的典型场景剖析从事Java开发这些年我见过太多因为事务配置不当导致的线上事故。上周团队里一个新人写的订单服务就出现了金额扣减成功但库存未更新的严重问题排查后发现是事务失效导致的。今天我就结合自己踩过的坑系统梳理Spring事务失效的12种典型场景每个场景都会给出可复现的测试用例。重要提示所有示例基于Spring Boot 2.7 JDK 11环境使用Transactional注解的默认隔离级别ISOLATION_DEFAULT和传播行为PROPAGATION_REQUIRED1.1 自调用问题这是最常见的失效场景之一。当类内部方法调用同类中带有Transactional注解的方法时事务不会生效Service public class OrderService { public void createOrder(OrderDTO dto) { // 自调用导致事务失效 this.deductStock(dto); } Transactional public void deductStock(OrderDTO dto) { // 库存扣减操作 } }失效原因Spring事务基于AOP实现自调用会绕过代理对象。解决方案有三种将方法拆分到不同类通过ApplicationContext获取代理对象使用AspectJ模式替代动态代理1.2 异常类型不匹配Transactional public void processPayment() { try { paymentDao.updateStatus(); // 这里抛出IOException } catch (Exception e) { // 吞掉异常 } }关键点默认只对RuntimeException和Error回滚检查异常需要明确指定rollbackFor不要catch后不处理异常1.3 数据库引擎不支持曾遇到过使用MySQL的MyISAM引擎导致事务失效的案例CREATE TABLE account ( id BIGINT PRIMARY KEY, balance DECIMAL(10,2) ) ENGINEMyISAM; -- 必须改为InnoDB2. 传播行为配置不当引发的陷阱2.1 REQUIRES_NEW误用Transactional public void parentMethod() { childService.childMethod(); // 传播行为配置为REQUIRES_NEW throw new RuntimeException(); // 父事务回滚不影响子事务 }实测现象parentMethod回滚childMethod提交造成数据不一致2.2 NESTED的注意事项Transactional(propagation Propagation.NESTED) public void nestedOperation() { // 需要数据库支持保存点SQL Server不支持 }3. 事务隔离级别的深坑3.1 幻读问题重现Transactional(isolation Isolation.READ_COMMITTED) public void transfer() { // 第一次查询余额 BigDecimal balance1 accountDao.getBalance(); // 此时另一个事务插入新记录 accountDao.insertNewRecord(); // 第二次查询结果不同 BigDecimal balance2 accountDao.getBalance(); }解决方案使用SERIALIZABLE级别加悲观锁SELECT ... FOR UPDATE使用版本号控制4. 其他隐蔽的失效场景4.1 多数据源配置问题spring: datasource: primary: url: jdbc:mysql://localhost:3306/db1 secondary: url: jdbc:mysql://localhost:3306/db2必须为每个数据源单独配置事务管理器Bean Primary public PlatformTransactionManager primaryTM(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); }4.2 异步方法调用Transactional public void asyncOperation() { CompletableFuture.runAsync(() - { // 这里的事务不会生效 accountDao.update(); }); }5. 诊断事务失效的实用技巧5.1 日志排查法在application.yml中开启调试日志logging: level: org.springframework.transaction: DEBUG org.springframework.jdbc: TRACE典型的事务生效日志特征Creating new transaction with name [...] Acquired Connection [...] for JDBC transaction Initiating transaction commit5.2 测试验证模板推荐使用这个测试模板验证事务行为SpringBootTest class TransactionTest { Test void testTransaction() { try { service.transactionalMethod(); } catch (Exception e) { // 验证数据库状态 assertThat(repository.count()).isEqualTo(0); } } }6. 最佳实践总结注解放置原则优先加在具体方法而非类上接口上的注解可能不生效取决于代理方式异常处理规范Transactional(rollbackFor Exception.class) public void safeOperation() throws BusinessException { // ... }性能优化建议只对需要事务的方法加注解读操作使用Transactional(readOnlytrue)避免长事务设置timeout参数最后分享一个真实案例某金融系统在批量处理时未设置事务超时导致数据库连接池耗尽。添加timeout30后问题解决Transactional(timeout 30) public void batchProcess() { // 批量操作 }