Spring源碼剖析8:Spring事務概述
- 2019 年 11 月 26 日
- 筆記
原文出處: 張開濤
資料庫事務概述
事務首先是一系列操作組成的工作單元,該工作單元內的操作是不可分割的,即要麼所有操作都做,要麼所有操作都不做,這就是事務。
事務必需滿足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.ISOLATIONDEFAULT:這是默認值,表示使用底層資料庫的默認隔離級別。對大部分資料庫而言,通常這值就是TransactionDefinition.ISOLATIONREAD_COMMITTED。
- TransactionDefinition.ISOLATIONREADUNCOMMITTED:該隔離級別表示一個事務可以讀取另一個事務修改但還沒有提交的數據。該級別不能防止臟讀,不可重複讀和幻讀,因此很少使用該隔離級別。比如PostgreSQL實際上並沒有此級別。
- TransactionDefinition.ISOLATIONREADCOMMITTED:該隔離級別表示一個事務只能讀取另一個事務已經提交的數據。該級別可以防止臟讀,這也是大多數情況下的推薦值。
- TransactionDefinition.ISOLATIONREPEATABLEREAD:該隔離級別表示一個事務在整個過程中可以多次重複執行某個查詢,並且每次返回的記錄都相同。該級別可以防止臟讀和不可重複讀。
- TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止臟讀、不可重複讀以及幻讀。但是這將嚴重影響程式的性能。通常情況下也不會用到該級別。
事務傳播行為
所謂事務的傳播行為是指,如果在開始當前事務之前,一個事務上下文已經存在,此時有若干選項可以指定一個事務性方法的執行行為。在TransactionDefinition定義中包括了如下幾個表示傳播行為的常量:
- TransactionDefinition.PROPAGATION_REQUIRED:如果當前存在事務,則加入該事務;如果當前沒有事務,則創建一個新的事務。這是默認值。
- TransactionDefinition.PROPAGATIONREQUIRESNEW:創建一個新的事務,如果當前存在事務,則把當前事務掛起。
- TransactionDefinition.PROPAGATION_SUPPORTS:如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續運行。
- TransactionDefinition.PROPAGATIONNOTSUPPORTED:以非事務方式運行,如果當前存在事務,則把當前事務掛起。
- TransactionDefinition.PROPAGATION_NEVER:以非事務方式運行,如果當前存在事務,則拋出異常。
- TransactionDefinition.PROPAGATION_MANDATORY:如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出異常。
- TransactionDefinition.PROPAGATIONNESTED:如果當前存在事務,則創建一個事務作為當前事務的嵌套事務來運行;如果當前沒有事務,則該取值等價於TransactionDefinition.PROPAGATIONREQUIRED。
事務超時
所謂事務超時,就是指一個事務所允許執行的最長時間,如果超過該時間限制但事務還沒有完成,則自動回滾事務。在 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所示。

圖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方法時將匹配到事務通知中定義的「」中指定的事務屬性,而調用countAll方法時將匹配到事務通知中定義的「」中指定的事務屬性。
聲明式事務是如何實現事務管理的呢?還記不記得TransactionProxyFactoryBean實現配置式事務管理,配置式事務管理是通過代理方式實現,而聲明式事務管理同樣是通過AOP代理方式實現。
聲明式事務通過AOP代理方式實現事務管理,利用環繞通知TransactionInterceptor實現事務的開啟及關閉,而TransactionProxyFactoryBean內部也是通過該環繞通知實現的,因此可以認為是幫你定義了TransactionProxyFactoryBean,從而簡化事務管理。
了解了實現方式後,接下來詳細學習一下配置吧:
9.4.4配置詳解 聲明式事務管理通過配置來定義事務屬性,配置方式如下所示:
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.PROPAGATIONREQUIRED」可以使用「REQUIRED」指定;
isolation:事務隔離級別定義;默認為「DEFAULT」,其值可以通過TransactionDefinition的靜態隔離級別變數的「ISOLATION」後邊部分指定,如「TransactionDefinition. ISOLATIONDEFAULT」可以使用「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程式碼:12、接下來看一下多事務語義配置吧,聲明式事務最佳實踐中已經配置了通用事務屬性,因此可以針對需要其他事務屬性的業務方法進行特例化配置:
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;//省略importpublic 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提供的用於開啟對註解事務管理的支援,從而能識別Bean類上的@Transactional註解元數據,其具有以下屬性:
transaction-manager:指定事務管理器名字,默認為transactionManager,當使用其他名字時需要明確指定;proxy-target-class:表示將使用的程式碼機制,默認false表示使用JDK代理,如果為true將使用CGLIB代理 order:定義事務通知順序,默認Ordered.LOWEST_PRECEDENCE,表示將順序決定權交給AOP來處理。Spring使用@Transactional 來指定事務屬性,可以在介面、類或方法上指定,如果類和方法上都指定了@Transactional ,則方法上的事務屬性被優先使用,具體屬性如下:
value:指定事務管理器名字,默認使用指定的事務管理器,用於支援多事務管理器環境;propagation:指定事務傳播行為,默認為Required,使用Propagation.REQUIRED指定;isolation:指定事務隔離級別,默認為「DEFAULT」,使用Isolation.DEFAULT指定;readOnly:指定事務是否只讀,默認false表示事務非只讀;timeout:指定事務超時時間,以秒為單位,默認-1表示事務超時將依賴於底層事務系統;rollbackFor:指定一組異常類,遇到該類異常將回滾事務;rollbackForClassname:指定一組異常類名字,其含義與中的rollback-for屬性語義完全一樣;noRollbackFor:指定一組異常類,即使遇到該類異常也將提交事務,即不回滾事務;noRollbackForClassname:指定一組異常類名字,其含義與中的no-rollback-for屬性語義完全一樣;Spring提供的@Transactional 註解事務管理內部同樣利用環繞通知TransactionInterceptor實現事務的開啟及關閉。
使用@Transactional註解事務管理需要特別注意以下幾點:
如果在介面、實現類或方法上都指定了@Transactional 註解,則優先順序順序為方法>實現類>介面;建議只在實現類或實現類的方法上使用@Transactional,而不要在介面上使用,這是因為如果使用JDK代理機制是沒問題,因為其使用基於介面的代理;而使用使用CGLIB代理機制時就會遇到問題,因為其使用基於類的代理而不是介面,這是因為介面上的@Transactional註解是「不能繼承的」;
具體請參考基於JDK動態代理和CGLIB動態代理的實現Spring註解管理事務(@Trasactional)到底有什麼區別。
在Spring代理機制下(不管是JDK動態代理還是CGLIB代理),「自我調用」同樣不會應用相應的事務屬性,其語義和中一樣;
默認只對RuntimeException異常回滾;
在使用Spring代理時,默認只有在public可見度的方法的@Transactional 註解才是有效的,其它可見度(protected、private、包可見)的方法上即使有@Transactional 註解也不會應用這些事務屬性的,Spring也不會報錯,如果你非要使用非公共方法註解事務管理的話,可考慮使用AspectJ。