spring事务管理的一些注意点

总结一些自己最近在使用spring事务管理时碰到的一些注意点

 

一、关于目标对象内部方法自我调用时的一些情形和存在的问题

1、情形1:只给b方法上加事务注解,a方法上不加

目标类的接口和实现代码示例:

public interface AService {

    public void a();

    public void b();

}

 

@Service()

public class AServiceImpl implements AService{

    public void a() {

        this.b();

    }

    @Transactional(rollbackFor={Exception.class})

       public void b() {

         insert();

         update();

    }

}

 

 

只要给目标类AServiceImpl的某个方法加上注解@Transactional,spring就会为目标类生成对应的代理类,以后调用AServiceImpl中的所有方法都会先走代理类(即使调用未加事务注解的方法a,也会走代理类),即在通过getBean("AServiceImpl")获得的业务类时,实际上得到的是一个代理类,假设这个类叫做AServiceImplProxy ,spring为AServiceImpl生成的代理类类似于如下代码:

public class AServiceImplProxy implements AService{

    public void a() {

      //反射调用目标类的a方法

    }

    public void b() {

          //启动事务的代码

 

      //反射调用目标类的b方法

 

     //事务提交的代码

    }

}

 

 

 

由于目标类中只有b方法加入了事务管理,所以代理类中只为b方法加入了横切事务逻辑,spring事务管理的本质是通过aop为目标类生成动态代理类,并在需要进行事务管理的方法中加入事务管理的横切逻辑代码(如AServiceImplProxy中的b方法所示)。

调用getBean("AServiceImpl").a()时,实际上执行的是AServiceImplProxy.a(),代理类的a方法会通过反射调用目标类的a方法, 再在目标类的a方法中调用b方法,故最终a中调用的b方法是来自于AServiceImpl中的b方法,AServiceImpl的b方法并没有横切事务逻辑代码(切记:事务逻辑代码在代理类中,@Transactional只是标记此方法在代理类中要加入事务逻辑代码)。所以调用a方法时,b方法的事务会失效。

 

其实,在proxy对象与目标对象之间还有一个InvocationHandler对象(以jdk动态代理为例),真正的横切逻辑是放到InvocationHandler对象中的,调用逻辑分离到InvocationHandler中主要是为了构造出具有通用性和简单性的代理类,此处为了简化处理过程,统一放到代理对象中来说明,动态代理简化的调用关系图如下:

aop中存在方法嵌套调用时,相应的调用过程序列图如下:

 

2、情形2:给a方法加事务注解,b方法上加或不加

对1中的代码做修改,为a方法也加上事务注解:

 

@Service()

public class AServiceImpl implements AService{

   @Transactional(rollbackFor={Exception.class})

     public void a() {

        this.b();

    }

  @Transactional(rollbackFor={Exception.class})

    public void b() {

         insert();

         update();

    }

}

 

此时生成的代理类类似如下代码:

 

public class AServiceImplProxy implements AService{

    public void a() {

     //启动事务的代码

 

         //反射调用目标类的a方法

 

         //事务提交的代码

    }

    public void b() {

          //启动事务的代码

 

     //反射调用目标类的b方法

 

     //事务提交的代码

      }

}

 

即为a和b都加入了事务横切逻辑。在这种情况下,调用顺序还和1中情形类似,区别在于在反射调用目标对象的a方法前,会对a方法开启事务管理,虽然调用的b方法还是目标对象中没有加事务逻辑的代码,spring却会把b合并到a的事务中去,此时相当于只有一个事务。

如果再将目标类代码改为:

 

@Service()

public class AServiceImpl implements AService{

    @Transactional(rollbackFor={Exception.class})

       public void a() {

       this.b();

    }

 

      public void b() {

       insert();

       update();

    }

}

 

即只在a上加事务控制,由于b会合并到a的事务中,所以b中的逻辑也可以被事务管理。

 

由于a和b都合并到了a的事务中,所以这种情形下事务传递规则不适用。代理类中加了事务逻辑的b方法永远不会被调用。

 

那么问题来了,如果我想让b也执行自己的事务逻辑,即调用b时执行代理类中b方法的事务逻辑,该怎么办?

 

修改目标类中的a方法:

 

@Transactional(rollbackFor={Exception.class})

public void a() {

    ((AService) AopContext.currentProxy()).b();

    //即调用AOP代理对象的b方法即可执行事务切面进行事务增强

}

 

这时,就会强制要求调用代理类中的b方法,从而开启b上的事务,此时b事务上标注的事务传递规则也就可以生效了,详情参见:http://jinnianshilongnian.iteye.com/blog/1487235

个人觉得这种方法不太好,会污染业务逻辑代码,使代码变复杂。

 

还有一种办法就是接口下沉,把b方法分离到另一个接口中,从根源上避免目标对象内部方法自我调用。

 

二、try catch的问题

有时需要在业务逻辑代码中显式try catch包裹事务代码,以便在出现异常时进行一些别的处理。

目标类的接口和实现示例代码如下:

 

public interface AService {

      public void a();

}

 

@Service()

public class AServiceImpl implements AService{

    @Transactional(rollbackFor={Exception.class})

    public void a() {

        try{

                 insert();

             update();

             }catch(Exception e){

             }

    }

}

 

自己在代码中显式捕获异常会导致spring事务回滚失效,原因:spring事务是通过aop捕获到异常后再执行回滚,如果业务代码中显式捕获了异常,会导致spring捕获不到,回滚自然失败。

有如下几种解决办法:

(1)业务代码catch住异常后重新抛出,如:

 

public void a() throws Exception{

        try{

                   insert();

               update();

            }catch(Exception e){

                   throw new Exception(e);

            }

    }

 

不足是本方法的调用端也必须显式捕获异常。

(2)使用编程式事务显式回滚:

 

public void a() {

        try{

                   insert();

                   update();

             }catch(Exception e){

                    //显式回滚

                   TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

             }

    }

 

不足是事务控制代码会侵入业务代码,也正是因为编程式事务管理会侵入业务逻辑代码,所以才有了申明式事务管理。

(3)接口下沉,将需要事务控制的代码分到另一个接口方法中,如:

 

public interface BService {

    public void a();

    public void b();

}

 

@Service()

public class BServiceImpl implements BService{

    @Transactional(rollbackFor={Exception.class})

    public void b() {

         insert();

        update();

    }

}

 

相应的调用端a方法中变为:

public void a() throws Exception{

        try{

                      bService.b();

             }catch(Exception e){

                      throw new Exception(e);

       }

    }

 

 

时间: 2024-09-18 22:11:33

spring事务管理的一些注意点的相关文章

Spring事务管理高级应用难点剖析: 第2部分

联合军种作战的混乱 Spring 抽象的 DAO 体系兼容多种数据访问技术,它们各有特 色,各有千秋.像 Hibernate 是非常优秀的 ORM 实现方案,但对底层 SQL 的控制不太方便: 而 iBatis 则通过模板化技术让您方便地控制 SQL,但没有 Hibernate 那样高的开发效率:自 由度最高的当然是直接使用 Spring JDBC 莫属了,但是它也是最底层的,灵活的代价是代码的 繁复.很难说哪种数据访问技术是最优秀的,只有在某种特定的场景下,才能给出答案.所以 在一个应用中,往

Spring事务管理高级应用难点剖析,第1部分

概述 Spring 最成功,最吸引人的地方莫过于轻量级的声明式事务管理,仅此一点,它就宣告了 重量级 EJB 容器的覆灭.Spring 声明式事务管理将开发者从繁复的事务管理代码中解脱出来 ,专注于业务逻辑的开发上,这是一件可以被拿来顶礼膜拜的事情.但是,世界并未从此消停 ,开发人员需要面对的是层出不穷的应用场景,这些场景往往逾越了普通 Spring 技术书籍的 理想界定.因此,随着应用开发的深入,在使用经过 Spring 层层封装的声明式事务时,开发 人员越来越觉得自己坠入了迷雾,陷入了沼泽,

分布式事务系列(1.1)Spring事务管理器PlatformTransactionManager

1 系列目录 分布式事务系列(开篇)提出疑问和研究过程 分布式事务系列(1.1)Spring事务管理器PlatformTransactionManager源码分析 分布式事务系列(1.2)Spring事务体系 分布式事务系列(2.1)分布式事务模型与接口定义 分布式事务系列(3.1)jotm的分布式案例 分布式事务系列(3.2)jotm分布式事务源码分析 分布式事务系列(4.1)Atomikos的分布式案例 2 jdbc事务 2.1 例子 public void save(User user)

spring 事务管理让我费解的地方

问题描述 在用springmvc时,用到了spring事务管理<beanid="dataSource"class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close"><propertyname="driverClassName"><value>${jdbc.driver}</value></prop

Spring事务管理--多个ORM框架在使用时的情况分析

   公司的项目已经接近尾声了,总结一下项目中用到的技术,我发现项目中的有些东西还是挺模糊的,只是知道这么用就行了.并不清楚其中的原理.由于公司的项目比较老,是7年前的一个项目了,中间一直有人在维护,也是在这个过程中不断融入了新的东西,比如就项目的持久化这块来说,就用了ibatis.mybatis.hibernate.spring JDBC四种混合的框架.究其原因只能说是历史遗留问题,就不做过多的解释了.但是这么多持久化的框架如何协同工作的,尤其是事务的控制,一个系统中使用如此多的持久化框架是,

并发-mybatis spring事务管理问题

问题描述 mybatis spring事务管理问题 mybatis把一个对象查询出来,让后在对这个对象做相关业务操作,然后再保存这个对象, 这样有个问题就是,在并发情况下,这个对象不是数据库最新的数据,有方法可以避免吗? 解决方案 在对象查询前面做一次对象状态更新,标记正要更新的状态.然后再查询出来,做业务操作,保存时把标记复原. 解决方案二: 可以加锁,或者线程,锁住对象就行了 解决方案三: 可以加锁,或者线程,锁住对象就行了

Spring事务管理只对出现运行期异常进行回滚_java

一.结论 Spring的事务管理默认只对出现运行期异常(java.lang.RuntimeException及其子类)进行回滚. 如果一个方法抛出Exception或者Checked异常,Spring事务管理默认不进行回滚. 关于异常的分类一下详细介绍: 1.基本概念 看java的异常结构图  Throwable是所有异常的根,java.lang.Throwable Error是错误,java.lang.Error Exception是异常,java.lang.Exception 2.Excep

Spring事务管理回顾——基本概念

BackGround:            最近一直在面试,感觉spring的事务配置问的挺多的,再扯出来好好瞅瞅,争取做到秒杀面试官. 一,事务的基本概念        什么是事务?               逻辑上的一组操作,这组操作要么全都成功,要么全都失败.例如,我购买完一个课程,要进行支付,需要首先去我的账户表中减去需要支付的金额,然后更新订单状态,支付才算是成功,那么支付跟更新订单状态就要放在一个事务里面执行,要么全成功,要么全失败,不能出现那种我买了课程,结果订单还显示我未支付

Spring 事务管理高级应用难点剖析: 第 3 部分

本文是"Spring 事务管理高级应用难点剖析" 系列文章的第 3 部分,作者将继续深入剖析在实际 Spring 事务管理应用中容易遇见的一些难点,包括在使用 Spring JDBC 时如果直接获取 Connection,哪些情况会造成数据连接的泄漏与如何应对,以及除 Spring JDBC 外,其它数据访问技术数据连接泄漏的应对方案.   概述 对于应用开发者来说,数据连接泄漏无疑是一个可怕的梦魇.如果存在数据连接泄漏问题,应用程序将因数据连接资源的耗尽而崩溃,甚至还可能引起数据库的