你知道处理Spring 九大事务失效场景吗?

1、九大事务失效场景

1.1、数据库不支持事务

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

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

从源码的位置 org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#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;

    }

}

注意:这个自定义后置处理器要在 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 调用的,没有真正的去调用代理类

假设有段代码是这样的

<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 的事务所处理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值