• 你的@Transaction注解失效了吗?


    引言

    在项目中我们会经常使用@Transaction注解来进行事务控制,但是有时莫名没有这个事务控制没有起作用,在面试的时候,面试官也可能会问@Transaction注解在什么情况下会失效?今天我正好看到有一篇文章写到关于这个,所以就整理一下.

    一.事务

    我们知道在spring框架中要进行事务控制,我们有两种方式.一是【编程式事务控制】,二是【声明式事务控制】。

    编程式事务控制

    概念:顾名思义,就是我们可以通过编程代码的方式进行事务控制

    缺点:代码侵入性强

    实例:

    try {
        //TODO something
         transactionManager.commit(status);
    } catch (Exception e) {
        transactionManager.rollback(status);
        throw new InvoiceApplyException("异常失败");
    }

    声明式事务控制

    概念:基于AOP面向切面,它将具体业务与事务处理部分解耦。

    优点:事务控制的代码和我们具体业务的代码解耦,代码侵入性小。所以在实际开发过程中,这种方式也是使用最多。声明式事务也有两种方式,一是基于TX和AOP的xml配置文件方式,二是基于@Transaction注解

    二、@Transaction介绍

      1.@Transaction注解可以使用的地方

        @Transaction可以作用在接口,类,类方法中

    • 作用类:该类的public方法都配置相同的事务属性信息.
    • 作用类方法:当类配置了@Transaction,方法也配置@Transaction,方法的事务会覆盖类的事务配置信息
    • 作用接口:不推荐这种方法,因为一旦标注在Interface上并且配置了Spring AOP使用CGLib动态代理,将会导致@Transaction注解失效  
    @Transactional
     @RestController
     @RequestMapping
     public class MybatisPlusController {
         @Autowired
         private CityInfoDictMapper cityInfoDictMapper;
     
         @Transactional(rollbackFor = Exception.class)
         @GetMapping("/test")
        public String test() throws Exception {
            CityInfoDict cityInfoDict = new CityInfoDict();
            cityInfoDict.setParentCityId(2);
            cityInfoDict.setCityName("2");
            cityInfoDict.setCityLevel("2");
            cityInfoDict.setCityCode("2");
            int insert = cityInfoDictMapper.insert(cityInfoDict);
            return insert + "";
        }
    }

    2.@Transaction的属性

    ①propagation

    propagation 代表事务的传播行为.默认值Propagation.REQUIRED.

    Propagation.REQUIRED:如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务.(也就是说如果A方法和B方法都添加了注解,在默认传播模式下,A方法内部调用B方法,会把两个方法事务合并一个事务)

    Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行

    Propagation.MANDATORY:如果当前存在事务,则加入该事务,如果当前不存在事务,则抛出异常.

    Propagation.REQUIRES_NEW:重新创建一个新的事务,如果当前存在事务,暂停当前的事务.(当前A中的a方法默认Propagation.REQUIRED模式,类B中的b方法加上采用Propagation.REQUIRES_NEW模式,然后在a方法中调用b方法操作数据库,然而a方法抛出异常后,b方法并没有进行回滚,因为Propagation.REQUIRES_NEW会暂停a方法的事务)

    Propagation.NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,暂停当前的事务

    Propagation.NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常

    Propagation.NESTED:和Propagation.REQUIRED效果一样.

    ②isolation属性

    isolation:事务的隔离级别

    ③timeout属性

    事务的超时时间,默认值为-1.如果超过该时间限制但事务还没有完成,则自动回滚.

    ④readOnly属性

    指定事务是否为只读事务,默认为false;为了忽略哪些不需要事务的方法,比如读取数据,可以设置为true

    ⑤rollbackFor属性

    用于指定能够触发事务回滚的异常类型,可以指定多个异常类型

    ⑥noRollbackFor属性

    抛出指定的异常类型,不会改事务,也可以指定多个异常类型.

    三、@Transaction失效的场景

    ①应用到非public修饰的方法上.之所以会失效是因为Spring AOP代理是,如图所示,TransactionInterceptor(事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy的内部类)的intercept方法或者JDKDynamicAopProxy的invoke方法会间接调用AbstractFallbackTransactionAttributeSource的computeTransactionAttribute方法,获取Transaction注解的事务配置信息.

    protected TransactionAttribute computeTransactionAttribute(Method method,
        Class<?> targetClass) {
            // Don't allow no-public methods as required.
            if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
            return null;
    }

    此方法会检查目标方法的修饰符时候为public,不是public则不会获取@Transaction的属性配置信息.

    ②注解属性propagation属性错误.

    如果配置propagation属性=下面三个属性值,则不会进行事务回滚.

    TransactionDefinition.PROPAGATION_SUPPORTS

    TransactionDefinition.PROPAGATION_NOT_SUPPORTED

    TransactionDefinition.PROPAGATION_NEVER

    ③rollbackFor设置错误

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

    若在目标方法中抛出的异常时rollbackFor指定的异常的子类,事务同样会回滚.

    private int getDepth(Class<?> exceptionClass, int depth) {
             if (exceptionClass.getName().contains(this.exceptionName)) {
                 // Found it!
                 return depth;
     }
             // If we've gone as far as we can go and haven't found it...
             if (exceptionClass == Throwable.class) {
                 return -1;
    }
    return getDepth(exceptionClass.getSuperclass(), depth + 1);
    }

    ④同一个类中方法调用,导致@Transaction失效

    比如有一个类Test,它的一个方法A,A再调用本类的方法B(无论方法B是public还是private修饰),当方法没有声明注解事务,而B方法有,则尾部调用方法A之后,方法B的事务是不会起作用的.这是因为使用Spring AOP代理造成的,因为只有当事务方法被当前类以为的代码调用时,才会有spring生成的代理对象来管理.

    ⑤异常被你的catch"吃了"导致@Transaction失效.

    这样情况是最常见的一种@Transaction注解失效场景

         @Transactional
         private Integer A() throws Exception {
             int insert = 0;
             try {
                 CityInfoDict cityInfoDict = new CityInfoDict();
                 cityInfoDict.setCityName("2");
                 cityInfoDict.setParentCityId(2);
                 /**
                  * A 插入字段为 2的数据
                 */
                insert = cityInfoDictMapper.insert(cityInfoDict);
                /**
                 * B 插入字段为 3的数据
                 */
                b.insertB();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    如果B方法内部抛了异常,而A方法此时try catch了B方法的异常,那这个事务还能正常回滚吗?

    答案:不能!

    会抛出异常:org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

    因为当ServiceB中抛出了一个异常以后,ServiceB标志当前事务需要rollback.当时ServiceA中由于你手动的捕获这个异常并进行处理,ServiceA认为当前事务应该正常commit.此时就会出现了前后不一致,也就是这样,抛出了前片UnexceptedRollbackException异常,

    Spring事务是在调用业务方法之前开始的,业务方法执行完毕之后才执行commit或者rollback,是否是有执行取决于是否抛出runTime异常.如果抛出runTimeException并在你的业务方法中没有catch到的话,事务会回滚.

    在业务方法中一般不需要catch异常,如果非要catch一定要抛出throw new RuntimeException(),或者注解中指定抛异常类型@Transactional(rollbackFor=Exception.class),否则会导致事务失效,数据commit造成数据不一致,所以有些时候try catch反倒会画蛇添足。

    数据库引擎不支持事务

    这种情况出现的概率并不高,事务能否生效数据库引擎是否支持事务是关键。常用的MySQL数据库默认使用支持事务的innodb引擎。一旦数据库引擎切换成不支持事务的myisam,那事务就从根本上失效了。

    以上取自【marcozheng】公众号,https://mp.weixin.qq.com/s/enKOM3F_Xxg123HPMCFUPw,并加以整理.

  • 相关阅读:
    HP Nonstop SQLMX (SPJ) Stored Procedure in Javas Getting Started
    Term of Wall Street
    iOS自带地图纠偏问题
    Android 界面排版的5种方式
    Android底部导航栏
    Android中Intent传值与Bundle传值的区别详解
    Android开发EditText属性
    从点击一个链接到浏览器显示页面,这个过程中发生了什么?
    HTML的常用标签属性及使用时需注意的一些细节
    写一个简单的脚本,并在脚本生成的的文件中添加内容
  • 原文地址:https://www.cnblogs.com/lovehunterYjj/p/12588532.html
Copyright © 2020-2023  润新知