声明式事务在哪些情况下会失效?
三分恶面渣逆袭:声明式事务的几种失效的情况
1、@Transactional 应用在非 public 修饰的方法上
如果 Transactional 注解应用在非 public 修饰的方法上,Transactional 将会失效。
是因为在 Spring AOP 代理时,TransactionInterceptor (事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource 的 computeTransactionAttribute方法,获取 Transactional 注解的事务配置信息。
protected TransactionAttribute computeTransactionAttribute(Method method,
Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
}
此方法会检查目标方法的修饰符是否为 public,不是 public 则不会获取 @Transactional 的属性配置信息。
2、@Transactional 注解属性 propagation 设置错误
TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行;错误使用场景:在业务逻辑必须运行在事务环境下以确保数据一致性的情况下使用 SUPPORTS。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:总是以非事务方式执行,如果当前存在事务,则挂起该事务。错误使用场景:在需要事务支持的操作中使用 NOT_SUPPORTED。
TransactionDefinition.PROPAGATION_NEVER:总是以非事务方式执行,如果当前存在事务,则抛出异常。错误使用场景:在应该在事务环境下执行的操作中使用 NEVER。
3、@Transactional 注解属性 rollbackFor 设置错误
rollbackFor 用来指定能够触发事务回滚的异常类型。Spring 默认抛出未检查 unchecked 异常(继承自 RuntimeException 的异常)或者 Error 才回滚事务,其他异常不会触发回滚事务。
三分恶面渣逆袭:Spring默认支持的异常回滚
// 希望自定义的异常可以进行回滚
@Transactional(propagation= Propagation.REQUIRED,rollbackFor= MyException.class)
若在目标方法中抛出的异常是 rollbackFor 指定的异常的子类,事务同样会回滚。
4、同一个类中方法调用,导致@Transactional 失效
开发中避免不了会对同一个类里面的方法调用,比如有一个类 Test,它的一个方法 A,A 调用本类的方法 B(不论方法 B 是用 public 还是 private 修饰),但方法 A 没有声明注解事务,而 B 方法有。
则外部调用方法 A 之后,方法 B 的事务是不会起作用的。这也是经常犯错误的一个地方。
那为啥会出现这种情况呢?其实还是由 Spring AOP 代理造成的,因为只有事务方法被当前类以外的代码调用时,才会由 Spring 生成的代理对象来管理。
//@Transactional
@GetMapping("/test")
private Integer A() throws Exception {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setCityName("2");
/**
* B 插入字段为 3的数据
*/
this.insertB();
/**
* A 插入字段为 2的数据
*/
int insert = cityInfoDictMapper.insert(cityInfoDict);
return insert;
}
@Transactional()
public Integer insertB() throws Exception {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setCityName("3");
cityInfoDict.setParentCityId(3);
return cityInfoDictMapper.insert(cityInfoDict);
}
这种情况是最常见的一种@Transactional 注解失效场景。
@Transactional
private Integer A() throws Exception {
int insert = 0;
try {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setCityName("2");
cityInfoDict.setParentCityId(2);
/**
* A 插入字段为 2的数据
*/
insert = cityInfoDictMapper.insert(cityInfoDict);
/**
* B 插入字段为 3的数据
*/
b.insertB();
} catch (Exception e) {
e.printStackTrace();
}
}
如果 B 方法内部抛了异常,而 A 方法此时 try catch 了 B 方法的异常,那这个事务就不能正常回滚了,会抛出异常:
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
@Transactional
@GetMapping("/test")
public Integer A() throws Exception {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setCityName("2");
/**
* B 插入字段为 3的数据
*/
this.insertB();
/**
* A 插入字段为 2的数据
*/
int insert = cityInfoDictMapper.insert(cityInfoDict);
return insert;
}
@Transactional()
public Integer insertB() throws Exception {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setCityName("3");
cityInfoDict.setParentCityId(3);
return cityInfoDictMapper.insert(cityInfoDict);
}
在修改后的代码中(方法 A
改为 public
且保留 @Transactional
,insertB
仍通过 this.insertB()
自调用),事务的行为如下:
1. 事务是否生效?
方法
A
的事务会生效:因为它是public
且通过代理对象调用(例如通过 HTTP 请求触发@GetMapping
)。方法
insertB
的事务不会生效:虽然它是public
且带有@Transactional
,但由于自调用(this.insertB()
),Spring 的代理机制被绕过,导致事务注解失效。
2. 实际事务行为
所有数据库操作(
insertB
和A
中的插入)会在A
的事务中执行:外部调用
A()
时,Spring 代理会拦截并开启事务。在
A()
内部通过this.insertB()
调用insertB()
时,由于是自调用,insertB()
的@Transactional
会被忽略。insertB()
的插入操作会直接加入A()
的事务中。最终,所有操作在
A()
的同一事务中提交或回滚。
3. 关键问题:自调用导致的事务失效
为什么
insertB
的事务不生效?
Spring 的事务基于 AOP 代理,只有通过代理对象调用的方法才会触发事务逻辑。自调用(this.insertB()
)直接调用目标对象的方法,绕过了代理,因此insertB
的@Transactional
失效。传播行为的配置会生效吗?
如果insertB
设置了传播行为(例如@Transactional(propagation = Propagation.REQUIRES_NEW)
),该配置也会失效,因为自调用时 Spring 不处理事务注解。
4. 示例验证
假设 insertB
中抛出异常:
@Transactional
@GetMapping("/test")
public Integer A() throws Exception {
this.insertB(); // insertB 抛出异常
cityInfoDictMapper.insert(...); // 不会执行
return 1;
}
@Transactional
public Integer insertB() throws Exception {
cityInfoDictMapper.insert(...); // 插入操作
throw new Exception("回滚测试"); // 抛出异常
}
结果:由于
A()
的事务生效,insertB
的异常会导致A()
的事务回滚,所有操作撤销。原因:
insertB
的异常通过自调用传播到A()
的事务中,触发全局回滚。
5. 如何让 insertB
的事务生效?
需解决自调用问题:
将
insertB
移到其他 Bean 中,通过依赖注入调用:@Autowired private OtherService otherService; @Transactional public Integer A() { otherService.insertB(); // 通过代理调用 // ... }
通过代理对象调用自身方法(需启用
exposeProxy
):@Transactional public Integer A() { ((YourClass) AopContext.currentProxy()).insertB(); // 通过代理调用 // ... }
(需在启动类添加
@EnableAspectJAutoProxy(exposeProxy = true)
)
最终答案
方法
A
的事务会生效,所有操作(包括insertB
)在A
的事务中执行。insertB
的@Transactional
不生效(自调用绕过代理),其传播行为、隔离级别等配置均无效。若需要
insertB
独立事务,必须通过代理调用或拆分到其他 Bean 中。
在同一个类中,非事务方法(未标注 @Transactional
的方法)直接调用事务方法(标注 @Transactional
的方法)时,事务不会生效。这是因为 Spring 的事务管理基于 AOP 代理机制,而自调用(通过 this
调用)会绕过代理,导致事务注解失效。
总结
自调用事务方法不生效:非事务方法直接调用同类中的事务方法时,事务失效。
根本原因:绕过代理,未触发 Spring 事务逻辑。
解决方法:
将事务方法拆分到其他 Bean。
通过
AopContext.currentProxy()
获取代理对象调用。通过接口注入自身代理。
事务生效的核心原则:确保通过 Spring 代理对象调用事务方法。