• Spring中@Transactional失效问题


    Spring中@Transactional失效

    Spring中的声明式注解@Transactional很大程度的方便了开发者进行DB数据保存。但是在一些特殊情况下,可能会造成注解不是按想定的方式生效,这里说几种可能造成的几种情况。

    常见的几种情况:

    异常被捕获

    这是一种比较简单不过稍不注意也可能会犯的情况。
    Spring中事务提交还是回滚是根据调用的方法是否抛出异常来决定的,因此如果把异常捕获之后又不抛出的话,即使出了问题,事务还是会提交。

    @Autowired
    private ClassB b;
    
    @Autowired
    private ClassC c;
    
    @Transactional
    public void methodA(){
        try {
            b.methodB();
            c.methodC();
        } catch (Exception e) {
            
        }
    }
    

    上例中,想要的结果是b.methodB()c.methodC()同时提交或回滚,但是由于异常被捕获,即使在执行方法C的时候出现了异常,方法B的操作仍旧会生效。
    (如果方法A是一次转账,方法B是转账中的加钱操作,方法C是减钱操作,B和C只执行其中一个的话会导致总金额就发生了变化)

    @Transactional修饰了非public方法

    这种也是有可能犯的一种情况。
    @Transactional只能用于 public 的方法上,否则事务不会失效,如果要用在非 public 方法上,可以开启AspectJ 代理模式。(默认代理模式CGlib)
    CGlib是使用继承进行动态代理的,所以理论上protect方法无修饰符时应该也可以?没有测试,有空试一下。

    同一个类中的方法调用

    这是一种非常容易犯,又不容易察觉的情况。

    @Component
    public class A{
        @Transactional
        public void methodA() {
            methodB();
        }
        
        @Transactional
        public void methodB() {
            // do something
        }
    }
    

    上例中,methodB的事务是不生效的,因为这里是同一个类中的调用(更确切的说是同一个类同一个对象中),Spring事务的原理是调用时检查@Transactional注解,然后生成代理类进行事务管理,但是内部调用时不会生成代理类(或者说默认不会),因此也就无法进行事务管理。

    这种情况有好几种解决方法,下面会说到。

    非常见的情况

    非常见是指对@Transactional进行了一些属性的配置导致不生效。

    @Transactional 注解属性 rollbackFor 设置错误

    rollbackFor 可以指定能够触发事务回滚的异常类型。Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定rollbackFor属性。

    // 希望自定义的异常进行回滚
    @Transactional(propagation= Propagation.REQUIRED,rollbackFor= MyException.class)
    

    @Transactional 注解属性 propagation 设置错误

    这种失效是由于配置错误,若是错误的配置以下三种 propagation,事务将不会发生回滚。

    • PROPAGATION.SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
    • PROPAGATION.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
    • PROPAGATION.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

    propagation默认值PROPAGATION.REQUIRED:没有事务时创建事务。有事务了则加入该事务(相当于使用一个session)

    同一个类中事务方法调用解决方法

    方法拆分

    这是一种最简单的方法,也就是把上面例子中的methodB拆分到一个单独的类里面,这样就是一般情况下的事务调用。


    下面三种方法都是在methodB上添加@Transactional(propagation = Propagation.REQUIRES_NEW)之后进行的测试

    使用AspectJ代理

    具体操作就是,application.yml中需要配置spring.aop.auto :true,然后在启动类开启AspectJ代理,并暴露代理类:@EnableAspectJAutoProxy(exposeProxy = true)
    这样的话就可以在调用的时候获取到代理类,并进行方法调用:

    ((TestClassA)AopContext.currentProxy()).insertB();
    

    从ApplicationContext获取Bean

    这个原理应该是和上面一样的,直从ApplicationContext中获取到当前Bean,然后再调用方法:

    // applicationContext 可以自动注入
    applicationContext.getBean(TestClassA.class).insertB();
    

    注入自身

    @Component
    public class TestClassA {
        @Autowired
        private TestClassA testClassA;
    }
    

    用这种方法也可以使methodB的事务生效,但是需要注意的是,第一次生成的代理类和自动注入的代理类不是一个对象,也就是代码中thistestClassA不是同一个对象,并且testclassA中不会再次自动注入。

    所以如果methodB插入数据时需要当前对象的属性,这种方法便不能再使用


    总之,同类事务不生效是一种很容易疏忽的情况,具体怎么处理,还是要结合业务。

  • 相关阅读:
    Django创建超级用户出现错误
    如何创建单例设计模式
    运行Spark-shell,解决Unable to load native-hadoop library for your platform
    在linux上安装spark详细步骤
    Spark源码编译,官网学习
    linux安装httpd,做文件服务器
    在linux上安装Scala详细步骤
    hadoop运行wordcount实例,hdfs简单操作
    hadoop-2.6.0源码编译问题汇总
    hadoop-2.6.0源码编译
  • 原文地址:https://www.cnblogs.com/lixin-link/p/13731767.html
Copyright © 2020-2023  润新知