SpringBoot项目里@Transactional事务失效?这5个坑我帮你踩过了

张开发
2026/4/20 10:12:40 15 分钟阅读

分享文章

SpringBoot项目里@Transactional事务失效?这5个坑我帮你踩过了
SpringBoot项目里Transactional事务失效这5个坑我帮你踩过了最近在重构一个老项目的支付模块时我遇到了一个诡异的问题明明加了Transactional注解但用户余额扣减后订单却没创建成功。排查了整整两天才发现原来是被Spring事务的潜规则给坑了。今天就把这些容易踩坑的场景整理出来帮你避开这些隐形的陷阱。1. 内部方法调用事务注解的灯下黑上周团队里有个小伙伴跑来问我为什么我的updateUser()方法里调用的saveLog()方法上的Transactional没生效 这其实是Spring AOP代理机制导致的经典问题。Service public class UserService { public void updateUser(User user) { // 业务逻辑... this.saveLog(user); // 这里的事务注解会失效 } Transactional public void saveLog(User user) { logRepository.save(new Log(用户更新, user.getId())); } }为什么会失效Spring事务基于AOP实现通过代理对象拦截方法调用直接通过this调用会绕过代理机制内部调用时事务注解就像不存在一样解决方案对比表方案实现方式优点缺点自我注入Autowired private UserService self;简单直接容易产生循环依赖拆分Service将方法拆到不同Service符合单一职责类爆炸风险编程式事务使用TransactionTemplate灵活可控代码侵入性强实际项目中我通常会选择方案二。虽然要多建几个类但代码结构更清晰也方便后续扩展。2. 异常处理吞掉异常的事务杀手去年双十一大促时我们系统出现过一次资金对账不平的事故。原因就是下面这种代码Transactional public void processOrder(Order order) { try { orderService.update(order); inventoryService.deduct(order.getItems()); } catch (Exception e) { log.error(订单处理失败, e); // 异常被捕获且未重新抛出 } }关键知识点默认只对RuntimeException和Error回滚捕获异常后事务管理器收不到失败信号受检异常需要特别声明才会触发回滚正确姿势// 明确指定需要回滚的异常类型 Transactional(rollbackFor {BusinessException.class, Exception.class}) public void processOrder(Order order) throws Exception { try { orderService.update(order); inventoryService.deduct(order.getItems()); } catch (InventoryException e) { // 转换异常类型保证事务回滚 throw new BusinessException(库存不足, e); } }3. 方法可见性private方法的陷阱在代码审查时我发现有同事写了这样的代码Service public class PaymentService { Transactional private void savePaymentRecord(Payment payment) { // 保存支付记录 } }这会导致什么问题Spring通过CGLIB创建子类代理private方法无法被重写最终效果等同于没加事务注解可见性规则速查表修饰符事务是否生效推荐程度public✅ 生效⭐⭐⭐⭐⭐protected❌ 不生效⭐⭐默认(package)❌ 不生效⭐private❌ 不生效❌曾经有个生产问题排查了3小时最后发现就是因为把Transactional用在了protected方法上。建议团队统一约定事务方法必须用public修饰。4. 数据库引擎InnoDB的专属特性有一次帮朋友公司排查问题发现他们MySQL表用的MyISAM引擎导致事务完全不起作用。这其实是个很基础的坑但容易被忽略。如何确认引擎类型SHOW TABLE STATUS LIKE your_table_name;解决方案修改表引擎ALTER TABLE your_table ENGINEInnoDB;配置默认引擎在application.yml中spring: jpa: database-platform: org.hibernate.dialect.MySQL5InnoDBDialect特别提醒使用Spring Data JPA自动建表时记得检查生成的表引擎。我有次就因为没配方言导致测试环境用的H2内存数据库事务表现和生产环境不一致。5. 传播机制嵌套事务的玄机这是我们项目中最复杂的案例在一个批量处理任务中需要部分成功部分失败。代码如下Transactional public void batchProcess(ListItem items) { items.forEach(item - { try { processItem(item); // 需要独立事务 } catch (Exception e) { errorHandler.handle(e); } }); } Transactional(propagation Propagation.REQUIRES_NEW) public void processItem(Item item) { // 单个item处理逻辑 }实际表现当processItem抛出异常时整个batchProcess也会回滚没有达到部分成功的设计目标根本原因默认的REQUIRED传播机制会使内部方法加入外部事务即使指定REQUIRES_NEW也要通过代理对象调用才有效最终解决方案Service public class BatchService { Autowired private ItemService itemService; // 通过代理对象调用 Transactional public void batchProcess(ListItem items) { items.forEach(item - { try { itemService.processItem(item); } catch (Exception e) { errorHandler.handle(e); } }); } } Service public class ItemService { Transactional(propagation Propagation.REQUIRES_NEW) public void processItem(Item item) { // 实现逻辑 } }调试技巧如何验证事务是否生效当怀疑事务没生效时可以用这些方法验证查看实际SQL日志logging: level: org.springframework.jdbc.support.JdbcTransactionManager: DEBUG注入事务管理器检查Autowired private PlatformTransactionManager transactionManager; GetMapping(/check-tx) public String checkTx() { System.out.println(当前事务管理器: transactionManager.getClass().getName()); return 检查完成; }测试回滚行为Test Transactional public void testTransactionRollback() { repository.save(new Entity(test)); throw new RuntimeException(强制回滚); }建议在项目初期就建立事务测试用例我维护的一个金融项目就有专门的TransactionTestSuite包含20个边界场景测试。

更多文章