Spring| Spring的事務 聲明式事務


一. 聲明式事務概述

從上節編程式實現事務管理可以深刻體會到編程式事務的痛苦,即使通過代理配置方式也是不小的工作量。本節將介紹聲明式事務支持,使用該方式後最大的獲益是簡單,事務管理不再是令人痛苦的,而且此方式屬於無侵入式,對業務邏輯實現無影響。接下來先來看看聲明式事務如何實現吧。


二. 聲明式實現事務管理-tx:advice實現事務管理

1、定義業務邏輯實現,此處使用ConfigUserServiceImpl和ConfigAddressServiceImpl:

2、定義配置文件(chapter9/service/ applicationContext-service-declare.xml):

2.1、XML命名空間定義,定義用於事務支持的tx命名空間和AOP支持的aop命名空間

<beans xmlns="http://www.springframework.org/schema/beans"  
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
    xmlns:tx="http://www.springframework.org/schema/tx"  
      xmlns:aop="http://www.springframework.org/schema/aop"  
      xsi:schemaLocation="  
          http://www.springframework.org/schema/beans  
          http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
       http://www.springframework.org/schema/tx  
       http://www.springframework.org/schema/tx/spring-tx-3.0.xsd  
          http://www.springframework.org/schema/aop  
          http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">  

2.2、業務實現配置,非常簡單,使用以前定義的非侵入式業務實現

<bean id="userService" class="cn.javass.spring.chapter9.service.impl.ConfigUserServiceImpl">  
    <property name="userDao" ref="userDao"/>  
    <property name="addressService" ref="addressService"/>  
</bean>  
<bean id="addressService" class="cn.javass.spring.chapter9.service.impl.ConfigAddressServiceImpl">  
    <property name="addressDao" ref="addressDao"/>  
</bean>  

2.3、事務相關配置

<tx:advice id="txAdvice" transaction-manager="txManager">  
    <tx:attributes>  
        <tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED"/>  
        <tx:method name="*" propagation="REQUIRED" isolation="READ_COMMITTED" read-only="true"/>  
    </tx:attributes>  
</tx:advice>  
<aop:config>  
    <aop:pointcut id="serviceMethod" expression="execution(* cn..chapter9.service..*.*(..))"/>  
    <aop:advisor pointcut-ref="serviceMethod" advice-ref="txAdvice"/>  
</aop:config>  

  • <tx:advice>:事務通知定義,用於指定事務屬性,其中“transaction-manager”屬性指定事務管理器,並通過< tx:attributes >指定具體需要攔截的方法;
  • <tx:method name="save*">:表示將攔截以save開頭的方法,被攔截的方法將應用配置的事務屬性:propagation="REQUIRED"表示傳播行爲是Required,isolation="READ_COMMITTED"表示隔離級別是提交讀;
  • <tx:method name="*">:表示將攔截其他所有方法,被攔截的方法將應用配置的事務屬性:propagation="REQUIRED"表示傳播行爲是Required,isolation="READ_COMMITTED"表示隔離級別是提交讀,read-only="true"表示事務只讀;
  • <aop:config>:AOP相關配置:
  • <aop:pointcut/>:切入點定義,定義名爲"serviceMethod"的aspectj切入點,切入點表達式爲"execution(* cn…chapter9.service….(…))"表示攔截cn包及子包下的chapter9. service包及子包下的任何類的任何方法;
  • <aop:advisor>:Advisor定義,其中切入點爲serviceMethod,通知爲txAdvice。

從配置中可以看出,將對cn包及子包下的chapter9. service包及子包下的任何類的任何方法應用“txAdvice”通知指定的事務屬性。

3、修改測試方法並測試該配置方式是否好用:

將TransactionTest 類的testServiceTransaction測試方法拷貝一份命名爲testDeclareTransaction:
並在testDeclareTransaction測試方法內將:

classpath:chapter9/service/applicationContext-service.xml"替換爲:
classpath:chapter9/service/applicationContext-service-declare.xml"

4、執行測試:

測試正常通過,說明該方式能正常工作 ,當調用save方法時將匹配到事務通知中定義的<tx:method name="save*">中指定的事務屬性,而調用countAll方法時將匹配到事務通知中定義的<tx:method name="*">中指定的事務屬性。

聲明式事務是如何實現事務管理的呢?還記不記得TransactionProxyFactoryBean實現配置式事務管理,配置式事務管理是通過代理方式實現,而聲明式事務管理同樣是通過AOP代理方式實現。

聲明式事務通過AOP代理方式實現事務管理,利用環繞通知TransactionInterceptor實現事務的開啓及關閉,而TransactionProxyFactoryBean內部也是通過該環繞通知實現的,因此可以認爲是tx:tags/幫你定義了TransactionProxyFactoryBean,從而簡化事務管理。瞭解了實現方式後,接下來詳細學習一下配置吧:


三. tx:advice配置詳解

聲明式事務管理通過配置<tx:advice/>來定義事務屬性,配置方式如下所示:

<tx:advice id="……" transaction-manager="……">  
<tx:attributes>  
        <tx:method name="……"  
                           propagation=" REQUIRED"  
                           isolation="READ_COMMITTED"  
                           timeout="-1"  
                           read-only="false"  
                           no-rollback-for=""   
                           rollback-for=""/>  
        ……  
    </tx:attributes>  
</tx:advice>  
  • <tx:advice>
    id 用於指定此通知的名字,
    transaction-manager 用於指定事務管理器,默認的事務管理器名字爲“transactionManager”;
  • <tx:method>:用於定義事務屬性即相關聯的方法名;
    name:定義與事務屬性相關聯的方法名,將對匹配的方法應用定義的事務屬性,可以使用“”通配符來匹配一組或所有方法,如“save”將匹配以save開頭的方法,而“*”將匹配所有方法;

    propagation:事務傳播行爲定義,默認爲“REQUIRED”,表示Required,其值可以通過TransactionDefinition的靜態傳播行爲變量的“PROPAGATION_”後邊部分指定,如“TransactionDefinition.PROPAGATION_REQUIRED”可以使用“REQUIRED”指定;

    isolation:事務隔離級別定義;默認爲“DEFAULT”,其值可以通過TransactionDefinition的靜態隔離級別變量的“ISOLATION_”後邊部分指定,如“TransactionDefinition. ISOLATION_DEFAULT”可以使用“DEFAULT”指定;

    timeout:事務超時時間設置,單位爲秒,默認-1,表示事務超時將依賴於底層事務系統;

    read-only:事務只讀設置,默認爲false,表示不是隻讀;

    rollback-for:需要觸發回滾的異常定義,以“,”分割,默認任何RuntimeException 將導致事務回滾,而任何Checked Exception 將不導致事務回滾;異常名字定義和TransactionProxyFactoryBean中含義一樣

    no-rollback-for:不被觸發進行回滾的 Exception(s);以“,”分割;異常名字定義和TransactionProxyFactoryBean中含義一樣;

記不記得在配置方式中爲了解決“自我調用”而導致的不能設置正確的事務屬性問題,使用“((IUserService)AopContext.currentProxy()).otherTransactionMethod()”方式解決,在聲明式事務要得到支持需要使用<aop:config expose-proxy=“true”>來開啓。


四. 多事務語義配置及最佳實踐

什麼是多事務語義?說白了就是爲不同的Bean配置不同的事務屬性,因爲我們項目中不可能就幾個Bean,而可能很多,這可能需要爲Bean分組,爲不同組的Bean配置不同的事務語義。在Spring中,可以通過配置多切入點和多事務通知並通過不同方式組合使用即可。

1、首先看下聲明式事務配置的最佳實踐吧:

<tx:advice id="txAdvice" transaction-manager="txManager">  
<tx:attributes>  
           <tx:method name="save*" propagation="REQUIRED" />  
           <tx:method name="add*" propagation="REQUIRED" />  
           <tx:method name="create*" propagation="REQUIRED" />  
           <tx:method name="insert*" propagation="REQUIRED" />  
           <tx:method name="update*" propagation="REQUIRED" />  
           <tx:method name="merge*" propagation="REQUIRED" />  
           <tx:method name="del*" propagation="REQUIRED" />  
           <tx:method name="remove*" propagation="REQUIRED" />  
           <tx:method name="put*" propagation="REQUIRED" />  
           <tx:method name="get*" propagation="SUPPORTS" read-only="true" />  
           <tx:method name="count*" propagation="SUPPORTS" read-only="true" />  
          <tx:method name="find*" propagation="SUPPORTS" read-only="true" />  
          <tx:method name="list*" propagation="SUPPORTS" read-only="true" />  
          <tx:method name="*" propagation="SUPPORTS" read-only="true" />  
       </tx:attributes>  
</tx:advice>  
<aop:config>  
       <aop:pointcut id="txPointcut" expression="execution(* cn.javass..service.*.*(..))" />  
       <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" />  
</aop:config>  

該聲明式事務配置可以應付常見的CRUD接口定義,並實現事務管理,我們只需修改切入點表達式來攔截我們的業務實現從而對其應用事務屬性就可以了,如果還有更復雜的事務屬性直接添加即可,即
如果我們有一個batchSaveOrUpdate方法需要“REQUIRES_NEW”事務傳播行爲,則直接添加如下配置即可:

<tx:method name="batchSaveOrUpdate" propagation="REQUIRES_NEW" />  

2、接下來看一下多事務語義配置吧,聲明式事務最佳實踐中已經配置了通用事務屬性,因此可以針對需要其他事務屬性的業務方法進行特例化配置:

<tx:advice id="noTxAdvice" transaction-manager="txManager">  
    <tx:attributes>  
           <tx:method name="*" propagation="NEVER" />  
    </tx:attributes>  
</tx:advice>  
<aop:config>  
       <aop:pointcut id="noTxPointcut" expression="execution(* cn.javass..util.*.*())" />  
       <aop:advisor advice-ref="noTxPointcut" pointcut-ref="noTxAdvice" />  
</aop:config>  

該聲明將對切入點匹配的方法所在事務應用“Never”傳播行爲。

多事務語義配置時,切入點一定不要疊加,否則將應用兩次事務屬性,造成不必要的錯誤及麻煩。


五. 聲明式實現事務管理-@Transactional實現事務管理

對聲明式事務管理,Spring提供基於@Transactional註解方式來實現,但需要Java 5+。

註解方式是最簡單的事務配置方式,可以直接在Java源代碼中聲明事務屬性,且對於每一個業務類或方法如果需要事務都必須使用此註解。

接下來學習一下註解事務的使用吧:
1、定義業務邏輯實現:

package cn.javass.spring.chapter9.service.impl;  
//省略import  
public class AnnotationUserServiceImpl implements IUserService {  
    private IUserDao userDao;  
    private IAddressService addressService;  
    public void setUserDao(IUserDao userDao) {  
        this.userDao = userDao;  
    }  
    public void setAddressService(IAddressService addressService) {  
        this.addressService = addressService;  
    }  
    @Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED)  
    @Override  
    public void save(final UserModel user) {  
        userDao.save(user);  
        user.getAddress().setUserId(user.getId());  
        addressService.save(user.getAddress());  
    }  
    @Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED, readOnly=true)  
    @Override  
    public int countAll() {  
        return userDao.countAll();  
    }  
}  

2、定義配置文件(chapter9/service/ applicationContext-service-annotation.xml):

2.1、XML命名空間定義,定義用於事務支持的tx命名空間和AOP支持的aop命名空間:

<beans xmlns="http://www.springframework.org/schema/beans"  
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
    xmlns:tx="http://www.springframework.org/schema/tx"  
      xmlns:aop="http://www.springframework.org/schema/aop"  
      xsi:schemaLocation="  
          http://www.springframework.org/schema/beans  
          http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
       http://www.springframework.org/schema/tx  
       http://www.springframework.org/schema/tx/spring-tx-3.0.xsd  
          http://www.springframework.org/schema/aop  
          http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">  

2.2、業務實現配置,非常簡單,使用以前定義的非侵入式業務實現:

<bean id="userService" class="cn.javass.spring.chapter9.service.impl.ConfigUserServiceImpl">  
    <property name="userDao" ref="userDao"/>  
    <property name="addressService" ref="addressService"/>  
</bean>  
<bean id="addressService" class="cn.javass.spring.chapter9.service.impl.ConfigAddressServiceImpl">  
    <property name="addressDao" ref="addressDao"/>  
</bean> 

2.3、事務相關配置:

<tx:annotation-driven transaction-manager="txManager"/>   

使用如上配置已支持聲明式事務。

3、修改測試方法並測試該配置方式是否好用:
將TransactionTest 類的testServiceTransaction測試方法拷貝一份命名爲testAnntationTransactionTest:
將測試代碼片段:
classpath:chapter9/service/applicationContext-service.xml"替換爲:classpath:chapter9/service/applicationContext-service-annotation.xml"

將測試代碼段

userService.save(user);  

替換爲:

try {  
    userService.save(user);  
    Assert.fail();  
} catch (RuntimeException e) {  
}  
Assert.assertEquals(0, userService.countAll());  
Assert.assertEquals(0, addressService.countAll());  

4、執行測試,測試正常通過,說明該方式能正常工作,因爲在AnnotationAddressServiceImpl類的save方法中拋出異常,因此事務需要回滾,所以兩個countAll操作都返回0。


六. @Transactional配置詳解

Spring提供的<tx:annotation-driven/>用於開啓對註解事務管理的支持,從而能識別Bean類上的@Transactional註解元數據,其具有以下屬性:

  • transaction-manager:指定事務管理器名字,默認爲transactionManager,當使用其他名字時需要明確指定;
  • proxy-target-class:表示將使用的代碼機制,默認false表示使用JDK代理,如果爲true將使用CGLIB代理
  • order:定義事務通知順序,默認Ordered.LOWEST_PRECEDENCE,表示將順序決定權交給AOP來處理。

Spring使用@Transaction來指定事務屬性,可以在接口、類或方法上指定,如果類和方法上都指定了@Transaction,則方法上的事務屬性被優先使用,具體屬性如下:

  • value:指定事務管理器名字,默認使用<tx:annotation-driven/>指定的事務管理器,用於支持多事務管理器環境;
  • propagation:指定事務傳播行爲,默認爲Required,使用Propagation.REQUIRED指定;
  • isolation:指定事務隔離級別,默認爲“DEFAULT”,使用Isolation.DEFAULT指定;
  • readOnly:指定事務是否只讀,默認false表示事務非只讀;
  • timeout:指定事務超時時間,以秒爲單位,默認-1表示事務超時將依賴於底層事務系統;
  • rollbackFor:指定一組異常類,遇到該類異常將回滾事務;
  • rollbackForClassname:指定一組異常類名字,其含義與<tx:method>中的rollback-for屬性語義完全一樣;
  • noRollbackFor:指定一組異常類,即使遇到該類異常也將提交事務,即不回滾事務;
  • noRollbackForClassname:指定一組異常類名字,其含義與<tx:method>中的no-rollback-for屬性語義完全一樣;

Spring提供的@Transaction註解事務管理內部同樣利用環繞通知TransactionInterceptor實現事務的開啓及關閉。
使用@Transactional註解事務管理需要特別注意以下幾點:

  • 如果在接口、實現類或方法上都指定了@Transactional 註解,則優先級順序爲方法>實現類>接口;
  • 建議只在實現類或實現類的方法上使用@Transactional,而不要在接口上使用,這是因爲如果使用JDK代理機制是沒問題,因爲其使用基於接口的代理;而使用使用CGLIB代理機制時就會遇到問題,因爲其使用基於類的代理而不是接口,這是因爲接口上的@Transactional註解是“不能繼承的”;具體請參考基於JDK動態代理和CGLIB動態代理的實現Spring註解管理事務(@Trasactional)到底有什麼區別。
  • 在Spring代理機制下(不管是JDK動態代理還是CGLIB代理),“自我調用”同樣不會應用相應的事務屬性,其語義和<tx:tags>中一樣;
  • 默認只對RuntimeException異常回滾;
  • 在使用Spring代理時,默認只有在public可見度的方法的@Transactional 註解纔是有效的,其它可見度(protected、private、包可見)的方法上即使有@Transactional 註解也不會應用這些事務屬性的,Spring也不會報錯,如果你非要使用非公共方法註解事務管理的話,可考慮使用AspectJ。

七. 與其他AOP通知協作

Spring聲明式事務實現其實就是Spring AOP+線程綁定實現,利用AOP實現開啓和關閉事務,利用線程綁定(ThreadLocal)實現跨越多個方法實現事務傳播。
由於我們不可能只使用一個事務通知,可能還有其他類型事務通知,而且如果這些通知中需要事務支持怎麼辦?這就牽扯到通知執行順序的問題上了,因此如果可能與其他AOP通知協作的話,而且這些通知中需要使用聲明式事務管理支持,事務通知應該具有最高優先級。


八. 聲明式or編程式

編程式事務時不推薦的,即使有很少事務操作,Spring發展到現在,沒有理由使用編程式事務,只有在爲了深入理解Spring事務管理才需要學習編程式事務使用。
推薦使用聲明式事務,而且強烈推薦使用<tx:tags>方式的聲明式事務,因爲其是無侵入代碼的,可以配置模板化的事務屬性並運用到多個項目中。
而@Transaction註解事務,可以使用,不過作者更傾向於使用<tx:tags>聲明式事務。
能保證項目正常工作的事務配置就是最好的。


九. 混合事務管理

所謂混合事務管理就是混合多種數據訪問技術使用,如混合使用Spring JDBC + Hibernate,接下來讓我們學習一下常見混合事務管理:
1、 Hibernate + Spring JDBC/iBATIS:使用HibernateTransactionManager即可支持;
2、 JPA + Spring JDBC/iBATIS:使用JpaTransactionManager即可支持;
3、 JDO + Spring JDBC/iBATIS:使用JtaTransactionManager即可支持;

混合事務管理最大問題在於如果我們使用第三方ORM框架,如Hibernate,會遇到一級及二級緩存問題,尤其是二級緩存可能造成如使用Spring JDBC和Hibernate查詢出來的數據不一致等。
因此不建議使用這種混合使用和混合事務管理。


原創內容,轉載請註明出處【http://sishuok.com/forum/blogPost/list/0/2508.html】

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章