zoukankan      html  css  js  c++  java
  • 聊聊@Transactional 的失效场景,有哪些坑?

    先别急着回答,看完再说也不迟嘛。我们都知道在 Spring 项目中,我们可以直接使用注解 @Transactional 来标识一个事务方法。然而,你可能并不知道这个事务是不是按照你想的方式执行。下面我们就一起来看看几种情况,你以为的事务管理可能并不是你以为的事务管理。

    0 经典错误案例

        @Transactional
        void transfer() {
            try{
                //...
            } catch (Exception e){
                LOGGER.error(e.getMessage())
            }
        }

    1 抛出检查异常

    方法若是抛出检查异常,比如 fileNotFound 这种,事务是不会回滚的,原因也很简单,因为 @Transactional 注解默认的 rollbackFor 是运行时异常。这也就是为什么阿里的开发规范中要求一定要指定 rollbackFor 的原因。

    所以我们在使用这个注解的时候还是建议写明 rollbackFor 这样你是明确知道出现了什么异常才会回滚的。

    @Transactional(rollbackFor = Exception.class)

    2 错误的使用 try catch

    业务方法中使用了 try catch 捕获了异常,然后异常顺利出现,结果进入你的 catch 块中,你却没有抛出,结果事务没有正确被回滚。这里出现的原理是 Spring 使用了代理来实现事务管理,调用顺序是开启事务,执行目标方法,提交或回滚事务,虽然你的目标方法出现了异常,架不住你自己处理了,在代理类看来,目标方法没有抛出异常。所以事务也就正常提交。

        @Transactional(rollbackFor = Exception.class)
        public void transfer(int amount) {
            try{
                serviceB.sub(amount);
                serviceA.add(amount);
            } catch (Exception e) {
                LOGGER.error("error occur");
            }
        }
     

    正确做法有两种,一种是在 catch 中继续抛出异常,第二种是告诉 Spring 我的当前事务需要 rollback.

    // 1
    throw new RuntimeException(e);
    // 2
    TransactionInterceptor.currentTransactionStatus().setRollbackOnly();

    3 错误添加切面

    AOP 切面顺序导致事务不能正确回滚,原因是事务切面优先级最低,但如果自定义的切面优先级和它一样,则还是自定义切面在内层,这时若自定义切面没有正确抛出异常,在 catch 中吃掉了异常,此时就会出现和第二种情况类似的情况,代理类得不到异常信息,也就不会回滚。

    解决方案就是在切面里面也抛出异常,或者是将自定义的切面的优先级设置为更小。但是建议使用第一种在切面中抛出异常,不过话说回来,为什么我们要为一个已经有事务管理的方法添加切面呢…… 事务方法一般在 Service 层,我们可以为 Controller 层添加切面。

    4 @Transactional 事务默认只能加在 public 的方法上

    非 public 的方法会导致事务失效。Spring 为方法创建代理,添加事务通知,前提条件是该方法是 public 的,这点需要注意的就是要么设置方法为 public,要么设置可以为非 public 的方法添加事务通知。

    建议使用 public 方法而不是添加配置

        @Bean
        public TransactionAttributeSource transactionAttributeSource(){
            return new AnnotationTransactionAttributeSource(false);
        }

    5 调用本类方法导致传播行为失效

    同一个 Service 的两个方法之间调用,就会出现这个问题,原因还是在代理对象这里,我们期待的调用是一个代理类的调用,但是我们若是直接在方法中内部调用,不好意思,被调用的方法的事务失效,没有被 AOP 增强。

        @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES)
        public void a (){
            b();
        }
    ​
        @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
        public void b (){
            
        }

    改进方案就是自己调用自己,自己注入自己。你可能看见过这样的代码,就是为了解决这个问题的。这种解决方案最常见。

        @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES)
        public void a (){
            service.b();
        }

    还有一种方法可以通过 AopContext 拿到代理对象,然后再调用。这里要注意,使用这种方式需要开启暴露代理。

    @EnableAspectJAutoProxy(exposeProxy = true)
    public class Application {}
    ​
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public void a (){
        ((TestService)AopContext.currentProxy()).b();
    }

    关键是这里有一个注释说到不保证 AopContext 一定 work,呃呃呃 好吧,还是自己注入自己吧。

        /**
         * Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal}
         * for retrieval via the {@link org.springframework.aop.framework.AopContext} class.
         * Off by default, i.e. no guarantees that {@code AopContext} access will work.
         * @since 4.3.1
         */
        boolean exposeProxy() default false;

    Transactional 并没有保证原子性行为

    这个问题非常常见,我们总会以为加了事务管理,尤其是加了 Propagation.REQUIRES_NEW 之后我们的事务就会在方法执行之后提交事务,或是加了 synchronized 关键字之后,这就是一个原子操作了,这是不对的哈。为什么呢?还要从代理类说起,代理类开启事务,执行目标方法,提交事务。而不管是 REQUIRES_NEW 还是 synchronized 关键字都只是作用于目标方法,即使目标方法执行成功,可是事务还是没有提交呢。

    对于 insert,delete,update,select --- for update,语句来说,都是原子性的。但是 select 不是。

    解决方案就是扩大 synchronized 的范围,为整个代理方法加锁,而不是把锁加在目标方法上。也可以通过 SQL 来控制,保证操作的原子性,使用 select --- for update


    原作者:非正经程序员
    链接:https://juejin.cn/post/7023778837456486436

    作者:鲁班快跑

    出处:https://www.cnblogs.com/zhusf/p/15480364.html

    版权:本文版权归作者和博客园共有

    转载:您可以随意转载、摘录,但请在文章内注明作者和原文链接。

  • 相关阅读:
    大型web系统分布式架构
    与MSN聊天的PowerTalk两个示例
    PowerTalk的四个示例代码
    PowerTalk在十月份左右会有新的版本
    PowerTalk控件 制作 即时通信 聊天室 产品咨询系统 支持与MSN的控件
    PowerTalk有些对不住大家
    自动生成实体sql工具的IDEvs2005工具(源代码+程序)
    C#字符串类快速编译器
    小菜编程成长记系列
    一道简单的编程题,不过您做对了吗?
  • 原文地址:https://www.cnblogs.com/zhusf/p/15480364.html
Copyright © 2011-2022 走看看