spring事务高频痛点在于代理机制与业务写法的隐性冲突:public方法才被代理拦截;默认仅runtimeexception触发回滚;同类内调用绕过代理;传播行为配置不当导致嵌套异常。

Java事务高频痛点教程,核心不在“配”,而在“懂触发条件、破代理限制、控异常边界”。配置只是表象,真正卡住开发者的,是Spring事务底层AOP机制与业务写法之间的隐性冲突。下面直击最常踩的几个坑,每一条都对应真实生产场景。
方法必须是 public 才能被事务代理拦截
Spring的@Transactional依赖动态代理(JDK或CGLIB),而代理对象只能拦截public方法调用。如果在类内部调用private/protected/default修饰的方法,哪怕加了@Transactional,也完全不生效——因为压根没走代理。
- 错误写法:
private @Transactional void doTransfer() { ... } - 正确写法:改为
public @Transactional void doTransfer() { ... } - 更稳妥做法:把事务方法拆到另一个@Service类中,通过注入调用,确保走代理
默认只对 RuntimeException 回滚,Checked 异常会被吞掉
@Transactional 默认 rollbackFor = RuntimeException.class,意味着遇到 SQLException、IOException 等 checked 异常时,事务不会回滚,程序继续执行,数据就“半途而废”了。
- 典型误用:try-catch 吞掉 SQLException,还自以为“处理了异常”
- 修复方式:
@Transactional(rollbackFor = Exception.class)或精准指定rollbackFor = SQLException.class - 补充建议:避免在事务方法里自行 try-catch 并吞异常;如需处理,捕获后重新抛出 runtime 异常,或显式调用 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()
同类内调用破坏代理链,事务失效
同一个类里,方法A加了@Transactional,方法B调用A——这种调用是 this.doA(),属于目标对象内部调用,绕过了代理对象,事务不生效。
- 现象:明明A方法标了@Transactional,但B调A后出错,A里的DB操作没回滚
- 根本原因:代理只作用于外部对Bean的调用,不干预内部this调用
- 解法一:把A抽到另一个Service,由Spring注入后调用
- 解法二:在当前类中注入自身(
@Autowired private UserService self;),用self.doA()替代this.doA()
事务传播行为配置不当引发意外提交或嵌套失败
多个事务方法相互调用时,@Transactional的 propagation 属性决定它们如何“合流”。默认是 REQUIRED,但遇到 REQUIRES_NEW、NESTED、SUPPORTS 等场景,容易出现意料外的提交或隔离失效。
- 常见陷阱:serviceA.methodA() 调用 serviceB.methodB(),后者用了 REQUIRES_NEW,methodB回滚不影响methodA,但methodA可能已提交部分数据
- 关键原则:非必要不用 REQUIRES_NEW;嵌套事务慎用 NESTED(仅JDBC支持,Hibernate/JPA需确认底层驱动)
- 调试技巧:开启Spring日志
logging.level.org.springframework.transaction=DEBUG,观察“Creating new transaction”和“Participating in existing transaction”提示
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











