Spring源碼剖析8:Spring事務概述

原文出處: 張開濤

本系列文章將整理到我在GitHub上的《Java面試指南》倉庫,更多精彩內容請到我的倉庫裏查看

https://github.com/h2pl/Java-Tutorial

喜歡的話麻煩點下Star哈

文章將同步到我的個人博客:

www.how2playlife.com

本文是微信公衆號【Java技術江湖】的《Spring和SpringMVC源碼分析》其中一篇,本文部分內容來源於網絡,爲了把本文主題講得清晰透徹,也整合了很多我認爲不錯的技術博客內容,引用其中了一些比較好的博客文章,如有侵權,請聯繫作者。

該系列博文會告訴你如何從spring基礎入手,一步步地學習spring基礎和springmvc的框架知識,並上手進行項目實戰,spring框架是每一個Java工程師必須要學習和理解的知識點,進一步來說,你還需要掌握spring甚至是springmvc的源碼以及實現原理,才能更完整地瞭解整個spring技術體系,形成自己的知識框架。

後續還會有springboot和springcloud的技術專題,陸續爲大家帶來,敬請期待。

爲了更好地總結和檢驗你的學習成果,本系列文章也會提供部分知識點對應的面試題以及參考答案。

如果對本系列文章有什麼建議,或者是有什麼疑問的話,也可以關注公衆號【Java技術江湖】聯繫作者,歡迎你參與本系列博文的創作和修訂。

<!-- more -->

數據庫事務概述

事務首先是一系列操作組成的工作單元,該工作單元內的操作是不可分割的,即要麼所有操作都做,要麼所有操作都不做,這就是事務。

事務必需滿足ACID(原子性、一致性、隔離性和持久性)特性,缺一不可:

  • 原子性(Atomicity):即事務是不可分割的最小工作單元,事務內的操作要麼全做,要麼全不做;
  • 一致性(Consistency):在事務執行前數據庫的數據處於正確的狀態,而事務執行完成後數據庫的數據還是處於正確的狀態,即數據完整性約束沒有被破壞;如銀行轉帳,A轉帳給B,必須保證A的錢一定轉給B,一定不會出現A的錢轉了但B沒收到,否則數據庫的數據就處於不一致(不正確)的狀態。
  • 隔離性(Isolation):併發事務執行之間無影響,在一個事務內部的操作對其他事務是不產生影響,這需要事務隔離級別來指定隔離性;
  • 持久性(Durability):事務一旦執行成功,它對數據庫的數據的改變必須是永久的,不會因比如遇到系統故障或斷電造成數據不一致或丟失。

在實際項目開發中數據庫操作一般都是併發執行的,即有多個事務併發執行,併發執行就可能遇到問題,目前常見的問題如下:

  • 丟失更新:兩個事務同時更新一行數據,最後一個事務的更新會覆蓋掉第一個事務的更新,從而導致第一個事務更新的數據丟失,這是由於沒有加鎖造成的;
  • 髒讀:一個事務看到了另一個事務未提交的更新數據;
  • 不可重複讀:在同一事務中,多次讀取同一數據卻返回不同的結果;也就是有其他事務更改了這些數據;
  • 幻讀:一個事務在執行過程中讀取到了另一個事務已提交的插入數據;即在第一個事務開始時讀取到一批數據,但此後另一個事務又插入了新數據並提交,此時第一個事務又讀取這批數據但發現多了一條,即好像發生幻覺一樣。

爲了解決這些併發問題,需要通過數據庫隔離級別來解決,在標準SQL規範中定義了四種隔離級別:

  • 未提交讀(Read Uncommitted):最低隔離級別,一個事務能讀取到別的事務未提交的更新數據,很不安全,可能出現丟失更新、髒讀、不可重複讀、幻讀;
  • 提交讀(Read Committed):一個事務能讀取到別的事務提交的更新數據,不能看到未提交的更新數據,不可能可能出現丟失更新、髒讀,但可能出現不可重複讀、幻讀;
  • 可重複讀(Repeatable Read):保證同一事務中先後執行的多次查詢將返回同一結果,不受其他事務影響,可能可能出現丟失更新、髒讀、不可重複讀,但可能出現幻讀;
  • 序列化(Serializable):最高隔離級別,不允許事務併發執行,而必須串行化執行,最安全,不可能出現更新、髒讀、不可重複讀、幻讀。

隔離級別越高,數據庫事務併發執行性能越差,能處理的操作越少。因此在實際項目開發中爲了考慮併發性能一般使用提交讀隔離級別,它能避免丟失更新和髒讀,儘管不可重複讀和幻讀不能避免,但可以在可能出現的場合使用悲觀鎖或樂觀鎖來解決這些問題。

事務類型

數據庫事務類型有本地事務和分佈式事務:

  • 本地事務:就是普通事務,能保證單臺數據庫上的操作的ACID,被限定在一臺數據庫上;
  • 分佈式事務:涉及兩個或多個數據庫源的事務,即跨越多臺同類或異類數據庫的事務(由每臺數據庫的本地事務組成的),分佈式事務旨在保證這些本地事務的所有操作的ACID,使事務可以跨越多臺數據庫;

Java事務類型有JDBC事務和JTA事務:

  • JDBC事務:就是數據庫事務類型中的本地事務,通過Connection對象的控制來管理事務;
  • JTA事務:JTA指Java事務API(Java Transaction API),是Java EE數據庫事務規範, JTA只提供了事務管理接口,由應用程序服務器廠商(如WebSphere Application Server)提供實現,JTA事務比JDBC更強大,支持分佈式事務。

Java EE事務類型有本地事務和全局事務:

  • 本地事務:使用JDBC編程實現事務;
  • 全局事務:由應用程序服務器提供,使用JTA事務;

按是否通過編程實現事務有聲明式事務和編程式事務;

  • 聲明式事務: 通過註解或XML配置文件指定事務信息;
  • 編程式事務:通過編寫代碼實現事務。

Spring提供的事務管理

Spring框架最核心功能之一就是事務管理,而且提供一致的事務管理抽象,這能幫助我們:

  • 提供一致的編程式事務管理API,不管使用Spring JDBC框架還是集成第三方框架使用該API進行事務編程;
  • 無侵入式的聲明式事務支持。

Spring支持聲明式事務和編程式事務事務類型。

spring事務特性

spring所有的事務管理策略類都繼承自org.springframework.transaction.PlatformTransactionManager接口

其中TransactionDefinition接口定義以下特性:

事務隔離級別

  隔離級別是指若干個併發的事務之間的隔離程度。TransactionDefinition 接口中定義了五個表示隔離級別的常量:

  • TransactionDefinition.ISOLATION_DEFAULT:這是默認值,表示使用底層數據庫的默認隔離級別。對大部分數據庫而言,通常這值就是TransactionDefinition.ISOLATION_READ_COMMITTED。
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED:該隔離級別表示一個事務可以讀取另一個事務修改但還沒有提交的數據。該級別不能防止髒讀,不可重複讀和幻讀,因此很少使用該隔離級別。比如PostgreSQL實際上並沒有此級別。
  • TransactionDefinition.ISOLATION_READ_COMMITTED:該隔離級別表示一個事務只能讀取另一個事務已經提交的數據。該級別可以防止髒讀,這也是大多數情況下的推薦值。
  • TransactionDefinition.ISOLATION_REPEATABLE_READ:該隔離級別表示一個事務在整個過程中可以多次重複執行某個查詢,並且每次返回的記錄都相同。該級別可以防止髒讀和不可重複讀。
  • TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止髒讀、不可重複讀以及幻讀。但是這將嚴重影響程序的性能。通常情況下也不會用到該級別。

事務傳播行爲

      所謂事務的傳播行爲是指,如果在開始當前事務之前,一個事務上下文已經存在,此時有若干選項可以指定一個事務性方法的執行行爲。在TransactionDefinition定義中包括瞭如下幾個表示傳播行爲的常量:

  • TransactionDefinition.PROPAGATION_REQUIRED:如果當前存在事務,則加入該事務;如果當前沒有事務,則創建一個新的事務。這是默認值。
  • TransactionDefinition.PROPAGATION_REQUIRES_NEW:創建一個新的事務,如果當前存在事務,則把當前事務掛起。
  • TransactionDefinition.PROPAGATION_SUPPORTS:如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續運行。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事務方式運行,如果當前存在事務,則把當前事務掛起。
  • TransactionDefinition.PROPAGATION_NEVER:以非事務方式運行,如果當前存在事務,則拋出異常。
  • TransactionDefinition.PROPAGATION_MANDATORY:如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出異常。
  • TransactionDefinition.PROPAGATION_NESTED:如果當前存在事務,則創建一個事務作爲當前事務的嵌套事務來運行;如果當前沒有事務,則該取值等價於TransactionDefinition.PROPAGATION_REQUIRED。

事務超時

      所謂事務超時,就是指一個事務所允許執行的最長時間,如果超過該時間限制但事務還沒有完成,則自動回滾事務。在 TransactionDefinition 中以 int 的值來表示超時時間,其單位是秒。

  默認設置爲底層事務系統的超時值,如果底層數據庫事務系統沒有設置超時值,那麼就是none,沒有超時限制。

事務只讀屬性

      只讀事務用於客戶代碼只讀但不修改數據的情形,只讀事務用於特定情景下的優化,比如使用Hibernate的時候。

默認爲讀寫事務。

概述

Spring框架支持事務管理的核心是事務管理器抽象,對於不同的數據訪問框架(如Hibernate)通過實現策略接口PlatformTransactionManager,從而能支持各種數據訪問框架的事務管理,PlatformTransactionManager接口定義如下:

java代碼:


public interface PlatformTransactionManager {
       TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
       void commit(TransactionStatus status) throws TransactionException;
       void rollback(TransactionStatus status) throws TransactionException;
}
  • getTransaction():返回一個已經激活的事務或創建一個新的事務(根據給定的TransactionDefinition類型參數定義的事務屬性),返回的是TransactionStatus對象代表了當前事務的狀態,其中該方法拋出TransactionException(未檢查異常)表示事務由於某種原因失敗。
  • commit():用於提交TransactionStatus參數代表的事務,具體語義請參考Spring Javadoc;
  • rollback():用於回滾TransactionStatus參數代表的事務,具體語義請參考Spring Javadoc。

TransactionDefinition接口定義如下:

java代碼:


public interface TransactionDefinition {
       int getPropagationBehavior();
       int getIsolationLevel();
       int getTimeout();
       boolean isReadOnly();
       String getName();
}
  • getPropagationBehavior():返回定義的事務傳播行爲;
  • getIsolationLevel():返回定義的事務隔離級別;
  • getTimeout():返回定義的事務超時時間;
  • isReadOnly():返回定義的事務是否是隻讀的;
  • getName():返回定義的事務名字。

TransactionStatus接口定義如下:

java代碼:

public interface TransactionStatus extends SavepointManager {
       boolean isNewTransaction();
       boolean hasSavepoint();
       void setRollbackOnly();
       boolean isRollbackOnly();
       void flush();
       boolean isCompleted();
}
  • isNewTransaction():返回當前事務狀態是否是新事務;
  • hasSavepoint():返回當前事務是否有保存點;
  • setRollbackOnly():設置當前事務應該回滾;
  • isRollbackOnly(():返回當前事務是否應該回滾;
  • flush():用於刷新底層會話中的修改到數據庫,一般用於刷新如Hibernate/JPA的會話,可能對如JDBC類型的事務無任何影響;
  • isCompleted():當前事務否已經完成。

內置事務管理器實現

Spring提供了許多內置事務管理器實現:

  • DataSourceTransactionManager:位於org.springframework.jdbc.datasource包中,數據源事務管理器,提供對單個javax.sql.DataSource事務管理,用於Spring JDBC抽象框架、iBATIS或MyBatis框架的事務管理;
  • JdoTransactionManager:位於org.springframework.orm.jdo包中,提供對單個javax.jdo.PersistenceManagerFactory事務管理,用於集成JDO框架時的事務管理;
  • JpaTransactionManager:位於org.springframework.orm.jpa包中,提供對單個javax.persistence.EntityManagerFactory事務支持,用於集成JPA實現框架時的事務管理;
  • HibernateTransactionManager:位於org.springframework.orm.hibernate3包中,提供對單個org.hibernate.SessionFactory事務支持,用於集成Hibernate框架時的事務管理;該事務管理器只支持Hibernate3+版本,且Spring3.0+版本只支持Hibernate 3.2+版本;
  • JtaTransactionManager:位於org.springframework.transaction.jta包中,提供對分佈式事務管理的支持,並將事務管理委託給Java EE應用服務器事務管理器;
  • OC4JjtaTransactionManager:位於org.springframework.transaction.jta包中,Spring提供的對OC4J10.1.3+應用服務器事務管理器的適配器,此適配器用於對應用服務器提供的高級事務的支持;
  • WebSphereUowTransactionManager:位於org.springframework.transaction.jta包中,Spring提供的對WebSphere 6.0+應用服務器事務管理器的適配器,此適配器用於對應用服務器提供的高級事務的支持;
  • WebLogicJtaTransactionManager:位於org.springframework.transaction.jta包中,Spring提供的對WebLogic 8.1+應用服務器事務管理器的適配器,此適配器用於對應用服務器提供的高級事務的支持。

Spring不僅提供這些事務管理器,還提供對如JMS事務管理的管理器等,Spring提供一致的事務抽象如圖9-1所示。
Spring源碼剖析8:Spring事務概述

圖9-1 Spring事務管理器

接下來讓我們學習一下如何在Spring配置文件中定義事務管理器:

一、聲明對本地事務的支持:

a)JDBC及iBATIS、MyBatis框架事務管理器

java代碼:


<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

通過dataSource屬性指定需要事務管理的單個javax.sql.DataSource對象。

b)Jdo事務管理器

java代碼:


<bean id="txManager" class="org.springframework.orm.jdo.JdoTransactionManager">
    <property name="persistenceManagerFactory" ref="persistenceManagerFactory"/>
</bean>

通過persistenceManagerFactory屬性指定需要事務管理的javax.jdo.PersistenceManagerFactory對象。

c)Jpa事務管理器

java代碼:

bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>

通過entityManagerFactory屬性指定需要事務管理的javax.persistence.EntityManagerFactory對象。

還需要爲entityManagerFactory對象指定jpaDialect屬性,該屬性所對應的對象指定了如何獲取連接對象、開啓事務、關閉事務等事務管理相關的行爲。

java代碼:

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        ……
        <property name="jpaDialect" ref="jpaDialect"/>
</bean>
<bean id="jpaDialect" class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>

d)Hibernate事務管理器

java代碼:

<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

通過entityManagerFactory屬性指定需要事務管理的org.hibernate.SessionFactory對象。

聲明式事務

聲明式事務概述

從上節編程式實現事務管理可以深刻體會到編程式事務的痛苦,即使通過代理配置方式也是不小的工作量。

本節將介紹聲明式事務支持,使用該方式後最大的獲益是簡單,事務管理不再是令人痛苦的,而且此方式屬於無侵入式,對業務邏輯實現無影響。

接下來先來看看聲明式事務如何實現吧。

聲明式實現事務管理

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

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

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

java代碼:
<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、業務實現配置,非常簡單,使用以前定義的非侵入式業務實現:

java代碼:
<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、事務相關配置:

java代碼:
<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>

java代碼:


<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相關配置:
:切入點定義,定義名爲”serviceMethod”的aspectj切入點,切入點表達式爲”execution(* cn..chapter9.service..*.*(..))”表示攔截cn包及子包下的chapter9\. service包及子包下的任何類的任何方法;
:Advisor定義,其中切入點爲serviceMethod,通知爲txAdvice。
從配置中可以看出,將對cn包及子包下的chapter9\. service包及子包下的任何類的任何方法應用“txAdvice”通知指定的事務屬性。

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

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

並在testDeclareTransaction測試方法內將:

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

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

聲明式事務通過AOP代理方式實現事務管理,利用環繞通知TransactionInterceptor實現事務的開啓及關閉,而TransactionProxyFactoryBean內部也是通過該環繞通知實現的,因此可以認爲是<tx:tags/>幫你定義了TransactionProxyFactoryBean,從而簡化事務管理。

瞭解了實現方式後,接下來詳細學習一下配置吧:

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

java代碼:
<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()”方式解決,在聲明式事務要得到支持需要使用來開啓。

9.4.5 多事務語義配置及最佳實踐
什麼是多事務語義?說白了就是爲不同的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>

該聲明式事務配置可以應付常見的CRUD接口定義,並實現事務管理,我們只需修改切入點表達式來攔截我們的業務實現從而對其應用事務屬性就可以了,如果還有更復雜的事務屬性直接添加即可,即

如果我們有一個batchSaveOrUpdate方法需要“REQUIRES_NEW”事務傳播行爲,則直接添加如下配置即可:

java代碼:
1
<tx:method name="batchSaveOrUpdate" propagation="REQUIRES_NEW" />
2、接下來看一下多事務語義配置吧,聲明式事務最佳實踐中已經配置了通用事務屬性,因此可以針對需要其他事務屬性的業務方法進行特例化配置:

java代碼:
<tx:advice id="noTxAdvice" transaction-manager="txManager">
    <tx:attributes>
           <tx:method name="*" propagation="NEVER" />
    </tx:attributes>
</tx:advice>

該聲明將對切入點匹配的方法所在事務應用“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命名空間:

java代碼:

    <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、業務實現配置,非常簡單,使用以前定義的非侵入式業務實現:

java代碼:
<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、事務相關配置:


java代碼:
1
<tx:annotation-driven transaction-manager="txManager"/>
使用如上配置已支持聲明式事務。

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

將TransactionTest 類的testServiceTransaction測試方法拷貝一份命名爲testAnntationTransactionTest:

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。

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

transaction-manager:指定事務管理器名字,默認爲transactionManager,當使用其他名字時需要明確指定;
proxy-target-class:表示將使用的代碼機制,默認false表示使用JDK代理,如果爲true將使用CGLIB代理
order:定義事務通知順序,默認Ordered.LOWEST_PRECEDENCE,表示將順序決定權交給AOP來處理。
Spring使用@Transactional 來指定事務屬性,可以在接口、類或方法上指定,如果類和方法上都指定了@Transactional ,則方法上的事務屬性被優先使用,具體屬性如下:

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提供的@Transactional 註解事務管理內部同樣利用環繞通知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。

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