Spring 九大事务失效场景分析

1、九大事务失效场景

1.1、数据库不支持事务

像 MySQL 数据库中的 MyISAM 引擎就不支持事务。

1.3、被代理类没有被 Spring 所管理

从源码的位置 org.springframework.aop.framework.***.Abstract***Creator#postProcessAfterInitialization

<pre class="prettyprint hljs dart" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">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,这个连初始化的机会都没有。

比如在后置处理器中依赖注入:

<pre class="prettyprint hljs java" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">@Component public class TestBeanPostProcessor implements BeanPostProcessor, Ordered { @Autowired private IAiExtractService aiExtractService; @Override public int getOrder() { return 1;

    }

}

注意:这个自定义后置处理器要在 Abstract***Creator 后置处理器前执行才会出现这个问题,而 Abstract***Creator 就只实现了 BeanPostProcessor,执行的顺序算是靠后了。

为什么在 Abstract***Creator 之前依赖注入就会代理失效呢?因为 Spring 是先将后置处理器注册后再初始化剩余的 Bean 的,而在注册后置处理器的过程中依赖注入会初始化 Bean,而初始化会调用容器中现存的后置处理器,还没注册后置处理器自然就不会被代理了。
1.5、标注事务注解的起点不在抛出异常的范围内

可能有些人会有这个疑问,为什么事务的注解一般标注在 service 层而不是 controller 层,难道标注在控制层会事务失效?

no no no,因为一般业务逻辑都是放置在 service 层的,而从方法的调用开始,标注事务注解就是事务有效范围的开始,而因为大部分逻辑都在 service 层,controller 基本上就一行代码,一般不会抛异常,自然注解标注在 controller 和 service 就差不多了。

最最重要的是 service 层的代码可能会提供公共调用,如果我的上级没有事务注解,而我的注解又在 controller 层,service 层抛出的异常就没办法回滚了。

1.6、事务的起点是被 this 调用的,没有真正的去调用代理类

假设有段代码是这样的

<pre class="prettyprint hljs cpp" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">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 这个方法,这个是执行事务的具体代码。

<pre class="prettyprint hljs scala" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">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、抛出的异常与事务能够处理的异常不匹配

接着上面的代码:

<pre class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">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、未配置事务管理器

还是一种就是不正确的使用传播行为导致的事务失效,我们在下一节具体分析。

最后再补充一个小点点,大家觉得以下的事务会失效吗?

<pre class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">@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 的事务所处理。

#java#
全部评论
 九大事务失效场景,知道了,感谢楼主分享
点赞 回复 分享
发布于 2022-08-27 22:20 陕西

相关推荐

点赞 评论 收藏
分享
10-05 23:02
东北大学 Java
我说句实话啊:那时候看三个月培训班视频,随便做个项目背点八股,都能说3 40w是侮辱价
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务