原文出處: 張開濤html
事務首先是一系列操做組成的工做單元,該工做單元內的操做是不可分割的,即要麼全部操做都作,要麼全部操做都不作,這就是事務。java
事務必需知足ACID(原子性、一致性、隔離性和持久性)特性,缺一不可:程序員
在實際項目開發中數據庫操做通常都是併發執行的,即有多個事務併發執行,併發執行就可能遇到問題,目前常見的問題以下:面試
爲了解決這些併發問題,須要經過數據庫隔離級別來解決,在標準SQL規範中定義了四種隔離級別:spring
隔離級別越高,數據庫事務併發執行性能越差,能處理的操做越少。所以在實際項目開發中爲了考慮併發性能通常使用提交讀隔離級別,它能避免丟失更新和髒讀,儘管不可重複讀和幻讀不能避免,但能夠在可能出現的場合使用悲觀鎖或樂觀鎖來解決這些問題。sql
數據庫事務類型有本地事務和分佈式事務:數據庫
Java事務類型有JDBC事務和JTA事務:express
Java EE事務類型有本地事務和全局事務:編程
按是否經過編程實現事務有聲明式事務和編程式事務;後端
Spring框架最核心功能之一就是事務管理,並且提供一致的事務管理抽象,這能幫助咱們:
Spring支持聲明式事務和編程式事務事務類型。
spring事務特性
spring全部的事務管理策略類都繼承自org.springframework.transaction.PlatformTransactionManager接口
其中TransactionDefinition接口定義如下特性:
事務隔離級別
隔離級別是指若干個併發的事務之間的隔離程度。TransactionDefinition 接口中定義了五個表示隔離級別的常量:
事務傳播行爲
所謂事務的傳播行爲是指,若是在開始當前事務以前,一個事務上下文已經存在,此時有若干選項能夠指定一個事務性方法的執行行爲。在TransactionDefinition定義中包括了以下幾個表示傳播行爲的常量:
事務超時
所謂事務超時,就是指一個事務所容許執行的最長時間,若是超過該時間限制但事務尚未完成,則自動回滾事務。在 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; }
TransactionDefinition接口定義以下:
java代碼:
public interface TransactionDefinition { int getPropagationBehavior(); int getIsolationLevel(); int getTimeout(); boolean isReadOnly(); String getName(); }
TransactionStatus接口定義以下:
java代碼:
public interface TransactionStatus extends SavepointManager { boolean isNewTransaction(); boolean hasSavepoint(); void setRollbackOnly(); boolean isRollbackOnly(); void flush(); boolean isCompleted(); }
Spring提供了許多內置事務管理器實現:
Spring不只提供這些事務管理器,還提供對如JMS事務管理的管理器等,Spring提供一致的事務抽象如圖9-1所示。
圖9-1 Spring事務管理器
接下來讓咱們學習一下如何在Spring配置文件中定義事務管理器:
1、聲明對本地事務的支持:
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對象。
從上節編程式實現事務管理能夠深入體會到編程式事務的痛苦,即便經過代理配置方式也是不小的工做量。
本節將介紹聲明式事務支持,使用該方式後最大的獲益是簡單,事務管理再也不是使人痛苦的,並且此方式屬於無侵入式,對業務邏輯實現無影響。
接下來先來看看聲明式事務如何實現吧。
一、定義業務邏輯實現,此處使用ConfigUserServiceImpl和ConfigAddressServiceImpl:
二、定義配置文件(chapter9/service/ applicationContext-service-declare.xml):
2.一、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.二、業務實現配置,很是簡單,使用之前定義的非侵入式業務實現:
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.三、事務相關配置:
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代碼:
<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」通知指定的事務屬性。
三、修改測試方法並測試該配置方式是否好用:
將TransactionTest 類的testServiceTransaction測試方法拷貝一份命名爲testDeclareTransaction:
並在testDeclareTransaction測試方法內將:
四、執行測試,測試正常經過,說明該方式能正常工做,當調用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()」方式解決,在聲明式事務要獲得支持須要使用<aop:config expose-proxy=」true」>來開啓。
9.4.5 多事務語義配置及最佳實踐
什麼是多事務語義?說白了就是爲不一樣的Bean配置不一樣的事務屬性,由於咱們項目中不可能就幾個Bean,而可能不少,這可能須要爲Bean分組,爲不一樣組的Bean配置不一樣的事務語義。在Spring中,能夠經過配置多切入點和多事務通知並經過不一樣方式組合使用便可。
一、首先看下聲明式事務配置的最佳實踐吧: <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」事務傳播行爲,則直接添加以下配置便可:
java代碼:
1
<tx:method name="batchSaveOrUpdate" propagation="REQUIRES_NEW" />
二、接下來看一下多事務語義配置吧,聲明式事務最佳實踐中已經配置了通用事務屬性,所以能夠針對須要其餘事務屬性的業務方法進行特例化配置:
java代碼: <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」傳播行爲。
多事務語義配置時,切入點必定不要疊加,不然將應用兩次事務屬性,形成沒必要要的錯誤及麻煩。
對聲明式事務管理,Spring提供基於@Transactional註解方式來實現,但須要Java 5+。
註解方式是最簡單的事務配置方式,能夠直接在Java源代碼中聲明事務屬性,且對於每個業務類或方法若是須要事務都必須使用此註解。
接下來學習一下註解事務的使用吧:
一、定義業務邏輯實現:
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(); } }
二、定義配置文件(chapter9/service/ applicationContext-service-annotation.xml):
2.一、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.二、業務實現配置,很是簡單,使用之前定義的非侵入式業務實現:
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.三、事務相關配置:
java代碼: 1 <tx:annotation-driven transaction-manager="txManager"/> 使用如上配置已支持聲明式事務。 三、修改測試方法並測試該配置方式是否好用: 將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());
四、執行測試,測試正常經過,說明該方式能正常工做,由於在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。
微信公衆號【黃小斜】做者是螞蟻金服 JAVA 工程師,專一於 JAVA 後端技術棧:SpringBoot、SSM全家桶、MySQL、分佈式、中間件、微服務,同時也懂點投資理財,堅持學習和寫做,相信終身學習的力量!關注公衆號後回覆」架構師「便可領取 Java基礎、進階、項目和架構師等免費學習資料,更有數據庫、分佈式、微服務等熱門技術學習視頻,內容豐富,兼顧原理和實踐,另外也將贈送做者原創的Java學習指南、Java程序員面試指南等乾貨資源