zoukankan      html  css  js  c++  java
  • @Transactional spring事务无效的解决方案

    关于@Transactional注解 一般都认为要注意以下三点

    1 .在需要事务管理的地方加@Transactional 注解。@Transactional 注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上 。

    2 . @Transactional 注解只能应用到 public 可见度的方法上 。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错, 但是这个被注解的方法将不会展示已配置的事务设置。

    3 . 注意仅仅 @Transactional 注解的出现不足于开启事务行为,它仅仅 是一种元数据。必须在配置文件中使用配置元素,才真正开启了事务行为。

    最近在项目中发现注解无效,经过跟踪源代码发现了问题,于是在网上找到相同出现此问题的人,以下为原文,讲解的很详细:

    只要避开Spring目前的AOP实现上的限制,要么都声明要事务,要么分开成两个类,要么直接在方法里使用编程式事务 
    [问题] 
    Spring的声明式事务,我想就不用多介绍了吧,一句话“自从用了Spring AOP啊,事务管理真轻松啊,真轻松;事务管理代码没有了,脑不酸了,手不痛了,一口气全配上了事务;轻量级,测试起来也简单,嘿!”。不管从哪个角度看,轻量级声明式事务都是一件解放生产力的大好事。所以,我们“一直用它”。

      不过,最近的一个项目里,却碰到了一个事务管理上的问题:有一个服务类,其一个声明了事务的方法,里面做了三次插入SQL操作,但是在后面出错回滚时,却发现前面插入成功了,也是说,这个声明了事务的方法,实际上并没有真正启动事务!怎么回事呢?难道Spring的声明式事务失效了?

    [探幽] 
    其实以前也会碰到有人说,Spring的事务配置不起作用,但是根据第一反应和以往经验,我总会告诉他,肯定是你的配置有问题啦;所以这一次,我想也不会例外,大概是把事务注解配在了接口上而不是实现方法上,或者,如果是用XML声明方式的话,很可能是切入点的表达式没有配对。

     不过,在检查了他们的配置后,却发现没有配置问题,该起事务的实现方法上,用了@Transactional事务注解声明,XML里也配了注解驱动<tx:annotation-driven .../>,配置很正确啊,怎么会不起作用?
    
     我很纳闷,于是往下问:
     问1:其他方法有这种情况么?
     答1:没有。
     问2:这个方法有什么特别的么(以下简称方法B)?
     答2:就是调后台插了三条记录啊,没啥特别的。
     问3:这个方法是从Web层直接调用的吧?
     答3:不是,是这个Service类(以下简称ServiceA)的另外一个方法调过来的(以下简称方法A)。
     问4:哦,那个调用它的方法配了事务么(问题可能在这了)?
     答4:没有。
     问5:那WEB层的Action(用的是Struts2),调用的是没有声明事务的方法A,方法A再调用声明了事务的方法B?
     答5:对的。
     问6:你直接在方法A上加上事务声明看看
     答6:好。。。
    
     看来可能找到问题所在了,于是把@Transactional也加在方法A上,启动项目测试,结果是:事务正常生效,方法A和方法B都在一个事务里了。
    
     好了,现在总结一下现象:
     1、ServiceA类为Web层的Action服务
     2、Action调用了ServiceA的方法A,而方法A没有声明事务(原因是方法A本身比较耗时而又不需要事务)
     3、ServiceA的方法A调用了自己所在class的方法B,而方法B声明了事务,但是方法B的事务声明在这种情况失效了。
     4、如果在方法A上也声明事务,则在Action调用方法A时,事务生效,而方法B则自动参与了这个事务。 
    
       我让他先把A也加上事务声明,决定回来自己再测一下。
    
       这个问题,表面上是事务声明失效的问题,实质上很可能是Spring的AOP机制实现角度的问题。

    我想到很久以前研究Spring的AOP实现时发现的一个现象:对于以Cglib方式增强的AOP目标类,会创建两个对象,一个事Bean实例本身,一个是Cglib增强代理对象,而不仅仅是只有后者。我曾经疑惑过这一点,但当时没有再仔细探究下去。

      我们知道,Spring的AOP实现方式有两种:1、Java代理方式;2、Cglib动态增强方式,这两种方式在Spring中是可以无缝自由切换的。

    Java代理方式的优点是不依赖第三方jar包,缺点是不能代理类,只能代理接口。 
    Spring通过AopProxy接口,抽象了这两种实现,实现了一致的AOP方式: 
    这里写图片描述 
    现在看来,这种抽象同样带了一个缺陷,那就是抹杀了Cglib能够直接创建普通类的增强子类的能力,Spring相当于把Cglib动态生成的子类,当普通的代理类了,这也是为什么会创建两个对象的原因。下图显示了Spring的AOP代理类的实际调用过程: 
    这里写图片描述

    因此,从上面的分析可以看出,methodB没有被AopProxy通知到, 
    导致最终结果是: 
    被Spring的AOP增强的类,在同一个类的内部方法调用时,其被调用方法上的增强通知将不起作用。

      而这种结果,会造成什么影响呢:
      1:内部调用时,被调用方法的事务声明将不起作用
      2:换句话说,你在某个方法上声明它需要事务的时候,如果这个类还有其他开发者,你将不能保证这个方法真的会在事务环境中
      3:再换句话说,Spring的事务传播策略在内部方法调用时将不起作用。

    不管你希望某个方法需要单独事务,是RequiresNew,还是要嵌套事务,要Nested,等等,统统不起作用。 
    4:不仅仅是事务通知,所有你自己利用Spring实现的AOP通知,都会受到同样限制。。。。

    [解难]

      问题的原因已经找到,其实,我理想中的AOP实现,应该是下面这样:

    这里写图片描述

    只要一个Cglib增强对象就好,对于Java代理方式,我的选择是毫不犹豫的抛弃。 
    至于前面的事务问题,只要避开Spring目前的AOP实现上的限制,要么都声明要事务,要么分开成两个类,要么直接在方法里使用编程式事务,那么一切OK。

    本文转自:http://blog.csdn.net/gudejundd/article/details/54380141

    spring+springMVC,声明式事务失效,原因以及解决办法

    一.声明式事务配置:

    <context:component-scan base-package="com.ada.wuliu"/>
        <context:component-scan base-package="com.wechat"/>
        <tx:advice id="txAdvice" transaction-manager="txManager">
            <tx:attributes>
                <tx:method name="add*" propagation="REQUIRED" rollback-for="Exception" />
                <tx:method name="del*" propagation="REQUIRED" rollback-for="Exception" />
                <tx:method name="update*" propagation="REQUIRED"
                    rollback-for="Exception" />
                <tx:method name="updateOrderPayBackNotfiy" propagation="REQUIRED"
                    isolation="SERIALIZABLE" />
                <tx:method name="*" read-only="true" />
            </tx:attributes>
        </tx:advice>

    二.声明式事务失效,原因

    根本原因:由子容器扫描装配了@Service 注解的实例。

    spring的context是父子容器,由ServletContextListener 加载spring配置文件产生的是父容器,springMVC加载配置文件产生的是子容器,子容器对Controller进行扫描装配时装配了@Service注解的实例 (@Controller 实例依赖@Service实例),而该实例理应由父容器进行初始化以保证事务的增强处理,所以此时得到的将是原样的Service没有经过事务加强处理,故而没有事务处理能力。

    三.解决办法

    1.修改spring配置文件:

    <!-- 不扫描带有@Controller注解的类 ,让 springMVC 子容器加载。 -->
     <context:component-scan base-package="com.ada.wuliu">     
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>     
    </context:component-scan>

     其他事务不起作用:

    • @Transactional 注解属性 propagation 设置错误

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

    TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。 TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。 TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

    • @Transactional 注解属性 rollbackFor 设置错误

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

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

    若在目标方法中抛出的异常是 rollbackFor 指定的异常的子类,事务同样会回滚。Spring源码如下:

    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);
    }
    • 同一个类中方法调用,导致@Transactional失效

    开发中避免不了会对同一个类里面的方法调用,比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方。

    那为啥会出现这种情况?其实这还是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。

    //@Transactional
        @GetMapping("/test")
        private Integer A() throws Exception {
            CityInfoDict cityInfoDict = new CityInfoDict();
            cityInfoDict.setCityName("2");
            /**
             * B 插入字段为 3的数据
             */
            this.insertB();
            /**
             * A 插入字段为 2的数据
             */
            int insert = cityInfoDictMapper.insert(cityInfoDict);
    
            return insert;
        }
    
        @Transactional()
        public Integer insertB() throws Exception {
            CityInfoDict cityInfoDict = new CityInfoDict();
            cityInfoDict.setCityName("3");
            cityInfoDict.setParentCityId(3);
    
            return cityInfoDictMapper.insert(cityInfoDict);
        }
    • 异常被你的 catch“吃了”导致@Transactional失效

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

    @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。此时就出现了前后不一致,也就是因为这样,抛出了前面的UnexpectedRollbackException异常。

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

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

    • 数据库引擎不支持事务

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

    • 启用新线程调用本类中的方法,这个新线程不会加入事务管理
    • 没有事物的方法调用本类有事物方法也无效,一样的,需要改成走代理类即可
    • Dubbo的注解和spring的transtion注解一起用会使dubbo的注解无效

    https://www.cnblogs.com/lmjk/p/7655659.html

    https://juejin.im/post/5e72e97c6fb9a07cb346083f

  • 相关阅读:
    atitit.nfc 身份证 银行卡 芯片卡 解决方案 attilax总结
    atitit.php 流行框架 前三甲为:Laravel、Phalcon、Symfony2 attilax 总结
    Atitit.执行cmd 命令行 php
    Atitit. 图像处理jpg图片的压缩 清理垃圾图片 java版本
    atitit。企业组织与软件工程的策略 战略 趋势 原则 attilax 大总结
    atitit. 管理哲学 大毁灭 如何防止企业的自我毁灭
    Atitit.java的浏览器插件技术 Applet japplet attilax总结
    Atitit.jquery 版本新特性attilax总结
    Atitit. 软件开发中的管理哲学一个伟大的事业必然是过程导向为主 过程导向 vs 结果导向
    (转)获取手机的IMEI号
  • 原文地址:https://www.cnblogs.com/nizuimeiabc1/p/7997638.html
Copyright © 2011-2022 走看看