spring的嵌套事务

一>  事务配置

 

Spring动态代理的一个重要特征是,它是针对接口的,所以我们的dao要通过动态代理来让spring接管事务,就必须在dao前面抽象出一个接口,当然如果没有这样的接口,那么spring会使用CGLIB来解决问题。

    一般地,使用Spring框架时,可在其applicationContext.xml文件中声明其对hibernate事务的使用:

    <bean id="tranManager"  
            class="org.springframework.orm.hibernate3.HibernateTransactionManager">  
            <property name="sessionFactory">  
                <ref bean="SessionFactoryID" />  
            </property>  
    </bean>  
    <bean id="transactionInterceptor"  
            class="org.springframework.transaction.interceptor.TransactionInterceptor">  
            <property name="transactionManager">  
                <ref bean="tranManager" />  
            </property>  
            <property name="transactionAttributes">  
                <props>  
                    <prop key="*">PROPAGATION_REQUIRED</prop>  
                </props>  
            </property>  
    </bean>  
    <bean id="proxyCreator"  
            class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">  
            <property name="interceptorNames">  
                <list>  
                    <value>transactionInterceptor</value>  
                </list>  
            </property>  
            <property name="beanNames">  
                <list>  
                    <value>*Biz</value>  
                </list>  
            </property>  
        </bean>  
          

上述配置是针对Biz后缀的所有接口类中声明的方法进行了事务配置,其事务的传播策略为PROPAGATION_REQUIRED,在在 spring 中一共定义了六种事务传播属性:

PROPAGATION_REQUIRED -- 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS -- 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY -- 支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW -- 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED -- 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER -- 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED -- 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。

二>  事务嵌套

 

在我所见过的误解中, 最常见的是下面这种:

引用
假如有两个业务接口 ServiceA 和 ServiceB, 其中 ServiceA 中有一个方法实现如下:

    /** 
    * 事务属性配置为 PROPAGATION_REQUIRED 
    */  
    void methodA() {  
    // 调用 ServiceB 的方法  
    ServiceB.methodB();  
    }   

那么如果 ServiceB 的 methodB  如果配置了事务, 就必须配置为 PROPAGATION_NESTED


这种想法可能害了不少人, 认为 Service 之间应该避免互相调用, 其实根本不用担心这点,PROPAGATION_REQUIRED 已经说得很明白,
如果当前线程中已经存在事务, 方法调用会加入此事务, 如果当前没有事务,就新建一个事务, 所以 ServiceB#methodB()的事务只要遵循最普通的规则配置为 PROPAGATION_REQUIRED 即可, 如果 ServiceB#methodB(我们称之为内部事务, 为下文打下基础) 抛了异常, 那么 ServiceA#methodA(我们称之为外部事务)如果没有特殊配置此异常时事务提交 (即 +MyCheckedException的用法), 那么整个事务是一定要 rollback 的, 什么Service 只能调 Dao 之类的言论纯属无稽之谈, spring 只负责配置了事务属性方法的拦截, 它怎么知道你这个方法是在 Service 还是 Dao 里 ?

 

也就是说, 最容易弄混淆的其实是 PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED, 那么这两种方式又有何区别呢? 我简单的翻译一下 Juergen Hoeller 的话 :
   
    PROPAGATION_REQUIRES_NEW 启动一个新的,不依赖于环境的 "内部" 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围,自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行.


    另一方面, PROPAGATION_NESTED 开始一个 "嵌套的"事务,  它是已经存在事务的一个真正的子事务. 潜套事务开始执行时,  它将取得一个 savepoint. 如果这个嵌套事务失败,我们将回滚到此 savepoint. 嵌套事务是外部事务的一部分, 只有外部事务结束后它才会被提交.

    由此可见, PROPAGATION_REQUIRES_NEW 和PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 潜套事务也会被 commit, 这个规则同样适用于roll back.
   

   
    那么外部事务如何利用嵌套事务的 savepoint 特性呢, 我们用代码来说话

     1. ServiceA {    
     2.         
     3.     /**  
     4.      * 事务属性配置为 PROPAGATION_REQUIRED  
     5.      */    
     6.     void methodA() {    
     7.         ServiceB.methodB();    
     8.     }    
     9.     
    10. }    
    11.     
    12. ServiceB {    
    13.         
    14.     /**  
    15.      * 事务属性配置为 PROPAGATION_REQUIRES_NEW  
    16.      */     
    17.     void methodB() {    
    18.     }    
    19.         
    20. }       
这种情况下, 因为 ServiceB#methodB 的事务属性为 PROPAGATION_REQUIRES_NEW,所以两者不会发生任何关系, ServiceA#methodA 和 ServiceB#methodB 不会因为对方的执行情况而影响事务的结果,因为它们根本就是两个事务, 在 ServiceB#methodB 执行时 ServiceA#methodA 的事务已经挂起了(关于事务挂起的内容已经超出了本文的讨论范围, 有时间我会再写一些挂起的文章) .

那么 PROPAGATION_NESTED 又是怎么回事呢? 继续看代码

1. ServiceA {    
 2.         
 3.     /**  
 4.      * 事务属性配置为 PROPAGATION_REQUIRED  
 5.      */    
 6.     void methodA() {    
 7.         ServiceB.methodB();    
 8.     }    
 9.     
10. }    
11.     
12. ServiceB {    
13.         
14.     /**  
15.      * 事务属性配置为 PROPAGATION_NESTED  
16.      */     
17.     void methodB() {    
18.     }    
19.         
20. }       

这种方式也是嵌套事务最有价值的地方, 它起到了分支执行的效果, 如果 ServiceB.methodB 失败, 那么执行ServiceC.methodC(), 而 ServiceB.methodB 已经回滚到它执行之前的 SavePoint,所以不会产生脏数据(相当于此方法从未执行过), 这种特性可以用在某些特殊的业务中, 而 PROPAGATION_REQUIRED 和PROPAGATION_REQUIRES_NEW 都没有办法做到这一点. (题外话 : 看到这种代码, 似乎似曾相识, 想起了prototype.js 中的 Try 函数 )

2. 代码不做任何修改, 那么如果内部事务(即 ServiceB#methodB) rollback, 那么首先 ServiceB.methodB 回滚到它执行之前的 SavePoint(在任何情况下都会如此),
   外部事务(即 ServiceA#methodA) 将根据具体的配置决定自己是 commit 还是 rollback (+MyCheckedException).
 

补充:

这种方式也是潜套事务最有价值的地方, 它起到了分支执行的效果, 如果 ServiceB.methodB 失败, 那么执行 ServiceC.methodC(),
而 ServiceB.methodB 已经回滚到它执行之前的 SavePoint, 所以不会产生脏数据, 这种特性可以用在某些特殊的业务中, 而 PROPAGATION_REQUIRED
和 PROPAGATION_REQUIRES_NEW 都没有办法做到这一点.

这里还是不明白,为什么PROPAGATION_REQUIRES_NEW不能做到呢?
如果ServiceB.methodB设成PROPAGATION_REQUIRES_NEW,当他执行失败时,ServiceA.methodA也有机会选择如何处理呀?

(可能)原因:PROPAGATION_NESTED 和PROPAGATION_REQUIRES_NEW 的主要区别在于,如果 ServiceB.methodB 完成执行并“提交”后,如果后面的ServiceA.methodA部分失败,那么如果设置成PROPAGATION_NESTED 那么ServiceB.methodB将需要回滚;如果设置成PROPAGATION_REQUIRES_NEW 那么ServiceB.methodB将不受影响而持久化事务内容。





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值