在Spring Boot项目里,@Transactional几乎是保障数据一致性的标配。但不少开发者在联调时发现,明明方法抛了异常,数据库记录却没有回滚。理解这种现象不能只停留在配置层面,必须回到Spring基于代理的事务增强机制上。Spring在容器启动时,对声明了事务的Bean生成代理对象,只有在通过代理调用目标方法时,事务拦截器才会开启、提交或回滚事务。一旦调用链脱离了代理控制,注解就失去了生效的土壤。

一、自调用导致代理失效
最常见的失效场景是同一个Service类内部方法互相调用。比如类中有methodA加了@Transactional,methodB没有,而methodB直接调用this.methodA()。此时this指向原始对象而非代理对象,事务拦截器根本没有被触发。很多人在写代码时习惯在一个业务方法里拆出多个子方法,若子方法承担写库操作且依赖事务,自调用就会让注解悄悄失效。
解决思路有两种。其一是将需要事务的方法抽到另一个Bean中,通过依赖注入调用,确保走代理。其二是使用AopContext.currentProxy()获取当前代理再调用,但需在启动类开启暴露代理配置。下面示例展示错误与正确写法:
// 错误:自调用,事务不生效
@Service
public class OrderService {
public void createOrder() {
this.saveOrder(); // 直接调用,绕过代理
}
@Transactional
public void saveOrder() {
// 写库操作
}
}
// 正确:注入自身代理或拆分Service
@Service
public class OrderService {
@Autowired
private OrderRepository repo;
public void createOrder() {
// 通过代理调用
((OrderService) AopContext.currentProxy()).saveOrder();
}
@Transactional
public void saveOrder() {
repo.save(new Order());
}
}
从设计角度看,自调用问题反映出对Spring AOP原理的误解。AOP本质是基于代理的运行时增强,而非编译期改写字节码。只要调用方持有的是原始实例引用,增强逻辑就不会介入。因此在多层业务封装时,应当优先用职责单一的Service拆分来规避此类问题。
二、方法非public或异常被吞
Spring的声明式事务默认只对public方法生效。如果在protected、private或包级方法上写@Transactional,以Spring Boot 2.x默认的代理方式,事务属性不会被读取。虽然IDE可能不会报错,但运行起来完全无事务保护。有些团队为了复用逻辑把事务方法写成private,结果在数据异常时排查半天找不到原因。
另一个隐蔽场景是异常被catch吞掉。事务回滚依赖于拦截器捕获到RuntimeException或指定异常。如果在方法内部用try-catch把异常吃了,且没有手动抛出新异常,代理层认为方法正常结束便会提交事务。以下代码演示了这种坑:
@Service
public class UserService {
@Transactional
public void updateUser() {
try {
// 可能抛出RuntimeException的写库
userRepo.update();
int i = 1 / 0;
} catch (Exception e) {
// 吞掉异常,事务不会回滚
log.error("error", e);
}
}
}
要避免此类问题,要么在catch中抛出运行时异常,要么使用TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()手动标记回滚。同时团队应建立代码规范,禁止在事务方法内静默吞异常。对于必须捕获处理的场景,建议将业务校验前置,让真正的数据写操作在方法尾部集中执行,降低意外提交风险。
三、数据库引擎与传播行为误区
即便Java代码层面事务配置正确,底层存储不支持事务也会让一切努力白费。例如MySQL的MyISAM引擎本身不支持事务,无论怎么加@Transactional都不会回滚。在Spring Boot集成老旧系统或自建表时,容易忽略建表语句里的ENGINE设置。排查时应当先确认表引擎是否为InnoDB,以及数据源是否禁用了自动提交以外的模式。
传播行为配置错误同样普遍。比如把本来需要独立事务的日志写入设成Propagation.REQUIRED,当外层事务回滚时,日志也被连带撤销,导致故障排查无迹可寻。反之,若核心业务用了Propagation.NOT_SUPPORTED,则直接挂起事务以非事务方式运行。理解每种传播属性的语义,是避免逻辑错乱的前提。
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void writeLog() {
// 独立事务,外层回滚不影响此处提交
logRepo.save(new Log());
}
还有几种场景也值得注意:一是异常类型不匹配,默认只回滚未受检异常,受检异常需配置rollbackFor;二是多线程中事务上下文不传递,子线程拿不到父线程连接;三是使用了非Spring管理的数据源,比如自己new的Connection;四是在单元测试中未启用事务上下文。综合来看,事务失效从来不是单点故障,而是代理机制、JVM调用、数据库能力三方交错的结果。只有把这些场景逐一映射到架构与编码规范中,才能真正让@Transactional成为可信赖的防线。
Spring_BootTransactional事务失效修改时间:2026-08-16 10:12:29