1、九大事务失效场景
首先来看看事务的具体执行过程
1.1、数据库不支持事务
像 MySQL 数据库中的 MyISAM 引擎就不支持事务。
1.3、被代理类没有被 Spring 所管理
从源码的位置 org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization
代码语言:less复制public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
可以看出 Spring 创建代理是需要获取容器中的 Bean 的,如果没有被 Spring 所管理,那自然就创建不了代理,事务自然也就不会生效了。
1.4、被代理的类过早实例化
在上一篇中讲了 如果 Bean 过早的进行实例化,那么在执行初始化的过程可能就不能被代理后置处理器处理。比如在 BeanDefinitionRegistryPostProcessor 实现类中使用 beanFactory.registerSingleton("test",new Test());
注册 Bean,这个连初始化的机会都没有。
比如在后置处理器中依赖注入:
代码语言:java复制@Component
public class TestBeanPostProcessor implements BeanPostProcessor, Ordered {
@Autowired
private IAiExtractService aiExtractService;
@Override
public int getOrder() {
return 1;
}
}
注意:这个自定义后置处理器要在 AbstractAutoProxyCreator 后置处理器前执行才会出现这个问题,而 AbstractAutoProxyCreator 就只实现了 BeanPostProcessor,执行的顺序算是靠后了。
为什么在 AbstractAutoProxyCreator 之前依赖注入就会代理失效呢?因为 Spring 是先将后置处理器注册后再初始化剩余的 Bean 的,而在注册后置处理器的过程中依赖注入会初始化 Bean,而初始化会调用容器中现存的后置处理器,还没注册后置处理器自然就不会被代理了。
1.5、标注事务注解的起点不在抛出异常的范围内
可能有些人会有这个疑问,为什么事务的注解一般标注在 service 层而不是 controller 层,难道标注在控制层会事务失效?
no no no,因为一般业务逻辑都是放置在 service 层的,而从方法的调用开始,标注事务注解就是事务有效范围的开始,而因为大部分逻辑都在 service 层,controller 基本上就一行代码,一般不会抛异常,自然注解标注在 controller 和 service 就差不多了。
最最重要的是 service 层的代码可能会提供公共调用,如果我的上级没有事务注解,而我的注解又在 controller 层,service 层抛出的异常就没办法回滚了。
1.6、事务的起点是被 this 调用的,没有真正的去调用代理类
假设有段代码是这样的
代码语言:c#复制public void test1(){
test2();
}
@Transactional(rollbackFor = Exception.class)
public void test2(){
throw new RuntimeException();
}
正常的事务代理是生成的动态代理类中调用目标方法的外层是有 try catch 包裹着的,如果出现异常会执行回滚操作。但是这里在事务的起点是通过 this 去调用目标方法的,也就是使用真实的类去调用目标方法,目标方法出现异常,自然就不能回滚操作了。需要使用注入的方式注入当前对象,然后使用代理类来调用目标方法。
1.7、方法抛出的异常在方法内捕获,没有被事务拦截器所拦截
可以找到 org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction
这个方法,这个是执行事务的具体代码。
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// target invocation exception
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
commitTransactionAfterReturning(txInfo);
这行就是调用目标方法的代码 retVal = invocation.proceedWithInvocation();
。如果你异常都捕获了,肯定是直接走到 commitTransactionAfterReturning 提交事务了啊。
1.8、抛出的异常与事务能够处理的异常不匹配
接着上面的代码:
代码语言:typescript复制protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
if (logger.isTraceEnabled()) {
logger.trace("Completing transaction for [" txInfo.getJoinpointIdentification()
"] after exception: " ex);
}
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
try {
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by rollback exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by rollback exception", ex);
throw ex2;
}
}
else {
// We don't roll back on this exception.
// Will still roll back if TransactionStatus.isRollbackOnly() is true.
try {
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by commit exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by commit exception", ex);
throw ex2;
}
}
}
}
public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error);
}
我们发现它只判断是不是 RuntimeException 或者 Error 的子类,否则的话就直接提交事务了,我们知道除了 RuntimeException 还有平级的异常有 SQLException、IOException,如果是他们的子类异常自然事务就不会回滚了。
所以在阿里的代码规范中就要求必须指名 Exception 的异常回滚类型,因为指定了指定的异常,如果在指定异常的层级下就会回滚,这些我在公众号中有具体讲解就不重复了。
1.9、未配置事务管理器
还是一种就是不正确的使用传播行为导致的事务失效,我们在下一节具体分析。
最后再补充一个小点点,大家觉得以下的事务会失效吗?
代码语言:less复制 @Autowired
private ICallbackService callbackService;
@Override
@Transactional(rollbackFor = Exception.class)
public void test1() {
callbackService.test2();
}
@Override
public void test2(){
// 正常插入数据
baseMapper.insert(new CallbackEntity().setContent("").setAccountId(1));
// 插入失败抛出异常
baseMapper.insert(new CallbackEntity());
}
实际上是不会失效的,上面说了,从调用开始 test1 是事务有效的起点,test2 发生异常然后抛出异常,因为是 RuntimeException,异常会不断的往上抛,最终被 test1 的事务所处理。
我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!