編程式事務:html
是侵入性事務管理,直接使用底層的PlatformTransactionManager、使用TransactionTemplate(Spring推薦使用);java
編程式事務管理對基於 POJO 的應用來講是惟一選擇。咱們須要在代碼中調用beginTransaction()、commit()、rollback()等事務管理相關的方法;程序員
聲明式事務:該事務是創建在AOP之上的,其本質是對方法先後進行攔截,而後在目標方法開始以前建立或加入一個事務,在執行完目標方法以後根據執行狀況提交或回滾事務。spring
Spring配置文件中關於事務配置老是由三個組成部分,分別是DataSource、TransactionManager和代理機制這三部分,不管哪一種配置方式,通常變化的只是代理機制這部分。sql
DataSource、TransactionManager這兩部分只是會根據數據訪問方式有所變化,好比使用Hibernate進行數據訪問時,DataSource實際爲SessionFactory,TransactionManager的實現爲HibernateTransactionManager。數據庫
根據代理機制的不一樣,總結了五種Spring事務的配置方式,以下圖:express
優勢:編程
編程式事務每次實現都要單獨實現,但業務量大且功能複雜時,使用編程性事務無疑是痛苦的;而聲明式事務不一樣,聲明式事務屬於非侵入性,不會影響業務邏輯的實現,只需在配置文件中作相關的事務規則聲明(或經過基於@Transactional註解的方式),即可以將事務規則應用到業務邏輯中;後端
非侵入式的開發方式,聲明式事務管理使業務代碼不受污染,一個普通的POJO對象,只要加上註解就能夠得到徹底的事務支持;spring-mvc
缺點:最細粒度只能是做用到方法級別,沒法作到像編程事務那樣能夠做用到代碼塊級別;
實現方式:
使用攔截器:基於TransactionInterceptor 類來實施聲明式事務管理功能(Spring最初提供的實現方式);
Bean和代理:基於 TransactionProxyFactoryBean的聲明式事務管理
全註解:基於@Transactional註解;
聲明式事務的約定流程:
Spring全部的事務管理策略類都繼承自org.springframework.transaction.PlatformTransactionManager接口,用於執行具體的事務操做。PlatformTransactionManager 接口中定義的主要方法以下:
public interface PlatformTransactionManager{ TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;//得到當前事務狀態 void commit(TransactionStatus status)throws TransactionException;//提交事務 void rollback(TransactionStatus status)throws TransactionException;//回滾事務 }
根據底層所使用的不一樣的持久化 API 或框架,PlatformTransactionManager 的主要實現類大體以下:
若是咱們使用JTA進行事務管理,咱們能夠經過 JNDI 和 Spring 的 JtaTransactionManager 來獲取一個容器管理的 DataSource。JtaTransactionManager 不須要知道 DataSource 和其餘特定的資源,由於它將使用容器提供的全局事務管理。而對於其餘事務管理器,好比DataSourceTransactionManager,在定義時須要提供底層的數據源做爲其屬性,也就是 DataSource。與 HibernateTransactionManager 對應的是 SessionFactory,與 JpaTransactionManager 對應的是 EntityManagerFactory 等等。
TransactionDefinition接口的主要方法以下:
public interface TransactionDefinition{ int getIsolationLevel();//返回事務的隔離級別,事務管理器依據它來控制另一個事務可以看到本事務內的哪些數據。 int getPropagationBehavior();//返回事務的傳播行爲,由是否有一個活動的事務來決定一個事務調用。 int getTimeout();//它返回事務必須在多少秒內完畢。 boolean isReadOnly();//事務是否僅僅讀,事務管理器能夠依據這個返回值進行優化。確保事務是僅僅讀的。 }
也許你會奇怪,爲何接口只提供了獲取屬性的方法,而沒有提供相關設置屬性的方法。其實道理很簡單,事務屬性的設置徹底是程序員控制的,所以程序員能夠自定義任何設置屬性的方法,並且保存屬性的字段也沒有任何要求。惟一的要求的是,Spring 進行事務操做的時候,經過調用以上接口提供的方法必須可以返回事務相關的屬性取值。
TransactionStatus 接口中定義的主要方法以下:
public interface TransactionStatus extends SavepointManager, Flushable { //是不是一個新的事務 boolean isNewTransaction(); //判斷是否有回滾點 boolean hasSavepoint(); //將一個事務標識爲不可提交的。在調用完setRollbackOnly()後只能被回滾 //在大多數狀況下,事務管理器會檢測到這一點,在它發現事務要提交時會馬上結束事務。 //調用完setRollbackOnly()後,數數據庫能夠繼續執行select,但不容許執行update語句,由於事務只能夠進行讀取操做,任何修改都不會被提交。 void setRollbackOnly(); boolean isRollbackOnly(); @Override void flush(); //判斷事務是否已經完成 boolean isCompleted(); }
TransactionDefinition接口定義如下特性:
事務隔離級別:指若干個併發的事務之間的隔離程度,TransactionDefinition接口中定義了5個表示隔離級別的常量
TransactionDefinition.ISOLATION_DEFAULT:默認值-1,表示使用底層數據庫的默認隔離級別,對大部分數據庫而言,一般這值就是TransactionDefinition.ISOLATION_READ_COMMITTED;
TransactionDefinition.ISOLATION_READ_UNCOMMITTED:該隔離級別表示一個事務能夠讀取另外一個事務修改但尚未提交的數據,該級別可能致使髒讀、不可重複讀和幻讀,所以不多使用該隔離級別,好比PostgreSQL實際上並無此級別;
TransactionDefinition.ISOLATION_READ_COMMITTED:(Oracle默認級別)該隔離級別表示一個事務只能讀取另外一個事務已經提交的數據,即容許從已經提交的併發事務讀取,該級別能夠防止髒讀,但幻讀和不可重複讀仍可能會發生;
TransactionDefinition.ISOLATION_REPEATABLE_READ:(MySQL默認級別)該隔離級別表示一個事務在整個過程當中能夠屢次重複執行某個查詢,而且每次返回的記錄都相同,即對相同字段的屢次讀取的結果是一致的,除非數據被當前事務本事改變。該級別能夠防止髒讀和不可重複讀,但幻讀仍可能發生;
TransactionDefinition.ISOLATION_SERIALIZABLE:(徹底服從ACID的隔離級別)全部的事務依次逐個執行,這樣事務之間就徹底不可能產生干擾,也就是說,該級別能夠防止髒讀、不可重複讀和幻讀,但嚴重影響程序的性能,由於它一般是經過徹底鎖定當前事務所涉及的數據表來完成的;
髒讀(Dirty read):發生在一個事務讀取了被另外一個事務改寫但還沒有提交的數據時。若是這些改變在稍後被回滾了,那麼第一個事務讀取的數據就會是無效的;
不可重複讀(Nonrepeatable read):發生在一個事務執行相同的查詢兩次或兩次以上,但每次查詢結果都不相同時。這一般是因爲另外一個併發事務在兩次查詢之間更新了數據。(不可重複讀重點在修改);
幻讀(Phantom reads):幻讀和不可重複讀類似。當一個事務(T1)讀取幾行記錄後,另外一個併發事務(T2)插入了一些記錄時,幻讀就發生了。在後來的查詢中,第一個事務(T1)就會發現一些原來沒有的額外記錄。(幻讀重點在新增或刪除);
事務傳播機制:事務的傳播性通常用在事務嵌套的場景,好比一個事務方法裏面調用了另一個事務方法,那麼兩個方法是各自做爲獨立的方法提交仍是內層的事務合併到外層的事務一塊兒提交,這就須要事務傳播機制的配置來肯定怎麼樣執行;在TransactionDefinition接口中定義瞭如下幾個表示傳播機制的常量,值爲0~6:
TransactionDefinition.PROPAGATION_REQUIRED:默認值,能知足絕大部分業務需求,若是外層有事務,則當前事務加入到外層事務,一塊提交,一塊回滾。若是外層沒有事務,新建一個事務執行;
TransactionDefinition.PROPAGATION_REQUIRES_NEW:該事務傳播機制是每次都會新開啓一個事務,同時把外層事務掛起,噹噹前事務執行完畢,恢復上層事務的執行。若是外層沒有事務,執行當前新開啓的事務便可;
TransactionDefinition.PROPAGATION_SUPPORTS:若是外層有事務,則加入外層事務;若是外層沒有事務,則直接以非事務的方式繼續運行。徹底依賴外層的事務;
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:該傳播機制不支持事務,若是外層存在事務則掛起,執行完當前代碼,則恢復外層事務,不管是否異常都不會回滾當前的代碼;
TransactionDefinition.PROPAGATION_NEVER:該傳播機制不支持外層事務,即若是外層有事務就拋出異常;
TransactionDefinition.PROPAGATION_MANDATORY:與NEVER相反,若是外層有事務,則加入外層事務,若是外層沒有事務,則拋出異常;
TransactionDefinition.PROPAGATION_NESTED:該傳播機制的特色是能夠保存狀態保存點,當前事務回滾到某一個點,從而避免全部的嵌套事務都回滾,即各自回滾各自的,若是子事務沒有把異常吃掉,基本仍是會引發所有回滾的;
傳播機制回答了這樣一個問題:一個新的事務應該被啓動仍是被掛起,或者是一個方法是否應該在事務性上下文中運行。
這裏須要指出的是,前面的六種事務傳播行爲是 Spring 從 EJB 中引入的,他們共享相同的概念。而 PROPAGATION_NESTED是 Spring 所特有的。以 PROPAGATION_NESTED 啓動的事務內嵌於外部事務中(若是存在外部事務的話),此時,內嵌事務並非一個獨立的事務,它依賴於外部事務的存在,只有經過外部的事務提交,才能引發內部事務的提交,嵌套的子事務不能單獨提交。若是熟悉 JDBC 中的保存點(SavePoint)的概念,那嵌套事務就很容易理解了,其實嵌套的子事務就是保存點的一個應用,一個事務中能夠包括多個保存點,每個嵌套子事務。另外,外部事務的回滾也會致使嵌套子事務的回滾。
掛起事務,指的是將當前事務的屬性如事務名稱,隔離級別等屬性保存在一個變量中,同時將當前線程中全部和事務相關的ThreadLocal變量設置爲從未開啓過線程同樣。Spring維護着一個當前線程的事務狀態,用來判斷當前線程是否在一個事務中以及在一個什麼樣的事務中,掛起事務後,當前線程的事務狀態就好像沒有事務。
事務超時:指一個事務所容許執行的最長時間,若是超過該時間限制但事務尚未完成,則自動回滾事務,在TransactionDefinition中以int的值來表示超時時間,其單位是秒;默認設置爲底層事務系統的超時值,若是底層數據庫事務系統沒有設置超時值,那麼就是none,沒有超時限制;
Spring事務回滾規則:默認配置下,Spring只有在拋出的異常爲運行時異常(runtime exception)時纔回滾該事務,也就是拋出的異常爲RuntimeException的子類(Error也會致使事務回滾),而拋出受檢查異常(checked exception)則不會致使事務回滾,不過能夠聲明在拋出哪些異常時回滾事務,包括checked異常,也能夠聲明哪些異常拋出時不回滾事務,即便異常是運行時異常,還能夠編程性的經過setRollbackOnly()方法來指示一個事務必須回滾,在調用完setRollbackOnly()後你所能執行的惟一操做就是回滾;
public class test { @Resource private PlatformTransactionManager txManager; @Resource private DataSource dataSource; private static JdbcTemplate jdbcTemplate; @Test public void testdelivery(){ //定義事務隔離級別,傳播行爲, DefaultTransactionDefinition def = new DefaultTransactionDefinition(); def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); //事務狀態類,經過PlatformTransactionManager的getTransaction方法根據事務定義獲取;獲取事務狀態後,Spring根據傳播行爲來決定如何開啓事務 TransactionStatus status = txManager.getTransaction(def); jdbcTemplate = new JdbcTemplate(dataSource); try { jdbcTemplate.update("insert into testtranstation(sd) values(?)", "1"); //提交status中綁定的事務 txManager.commit(status); } catch (RuntimeException e) { //回滾 txManager.rollback(status); } } }
如上所示,咱們在類中增長了兩個屬性:一個是 TransactionDefinition 類型的屬性,它用於定義一個事務;另外一個是 PlatformTransactionManager 類型的屬性,用於執行事務管理操做。
若是方法須要實施事務管理,咱們首先須要在方法開始執行前啓動一個事務,調用PlatformTransactionManager.getTransaction(…) 方法即可啓動一個事務。建立並啓動了事務以後,即可以開始編寫業務邏輯代碼,而後在適當的地方執行事務的提交或者回滾。
<?xml version="1.0" encoding="UTF-8"?> <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" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <description>數據源及事務配置</description> <!-- 數據源配置 --> <!-- 代理datasource,使其可以顯式獲取preparedStatement的參數值 --> <bean id="proxyDataSource" class="org.jdbcdslog.ConnectionPoolDataSourceProxy"> <property name="targetDSDirect" ref="dataSource"/> </bean> <!-- 配置事務管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="proxyDataSource" /> </bean> <!--事務模板 --> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="transactionManager"/> <!--ISOLATION_DEFAULT 表示由使用的數據庫決定 --> <property name="isolationLevelName" value="ISOLATION_DEFAULT"/> <property name="propagationBehaviorName" value="PROPAGATION_REQUIRED" /> <!-- <property name="timeout" value="30"/> --> </bean> <!-- 註解方式配置事物 --> <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/> </beans>
package com.hrh.initialize; import org.springframework.beans.factory.InitializingBean; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionTemplate; public class DataInitializer implements InitializingBean{ private TransactionTemplate transactionTemplate; private JdbcTemplate jdbcTemplate; @Override public void afterPropertiesSet() throws Exception { transactionTemplate.execute(new TransactionCallback<Object>() { @Override public Object doInTransaction(TransactionStatus status) { //建立保存點 Object savepoint = status.createSavepoint(); // DML執行 try { jdbcTemplate.execute("truncate table SET_RESOURCE"); jdbcTemplate.execute(String.format("INSERT INTO SET_RESOURCE VALUES ('%s','%s','%s','%s',%s,%s)", "100", "cAuthc", "/sample/component.html", "1", "null", "null")); jdbcTemplate.execute(String.format("INSERT INTO SET_RESOURCE VALUES ('%s','%s','%s','%s',%s,%s)", "992", "cAuthc", "/mt/rgroup/rgroup_read.html", "1", "null", "null")); } catch (Throwable e) { LOG.error("Error occured, cause by: {}", e.getMessage()); //經過TransactionStatus的setRollbackOnly()或rollbackToSavepoint(savepoint) 控制事務 status.setRollbackOnly(); // status.rollbackToSavepoint(savepoint); } return null; } }); } public void setTransactionTemplate(TransactionTemplate transactionTemplate) { this.transactionTemplate = transactionTemplate; } public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } }
@Override public void afterPropertiesSet() throws Exception { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { //字段sd爲int型,因此插入確定失敗報異常,自動回滾,表明TransactionTemplate自動管理事務 jdbcTemplate.update("insert into testtranstation(sd) values(?)", "餓死"); }} ); }
總結:
TransactionTemplate 的 execute() 方法有一個 TransactionCallback 類型的參數,該接口中定義了一個 doInTransaction() 方法,一般咱們以匿名內部類的方式實現 TransactionCallback 接口,並在其 doInTransaction() 方法中書寫業務邏輯代碼。這裏可使用默認的事務提交和回滾規則,這樣在業務代碼中就不須要顯式調用任何事務管理的 API。doInTransaction() 方法有一個TransactionStatus 類型的參數,咱們能夠在方法的任何位置調用該參數的 setRollbackOnly() 方法將事務標識爲回滾的,以執行事務回滾。
根據默認規則,若是在執行回調方法的過程當中拋出了未檢查異常,或者顯式調用了TransacationStatus.setRollbackOnly() 方法,則回滾事務;若是事務執行完成或者拋出了 checked 類型的異常,則提交事務。
TransactionCallback 接口有一個子接口 TransactionCallbackWithoutResult,該接口中定義了一個 doInTransactionWithoutResult() 方法,TransactionCallbackWithoutResult 接口主要用於事務過程當中不須要返回值的狀況。固然,對於不須要返回值的狀況,咱們仍然可使用 TransactionCallback 接口,並在方法中返回任意值便可。
<beans...> ...... <bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor"> <property name="transactionManager" ref="transactionManager"/> <property name="transactionAttributes"> <props> <prop key="add*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> <bean id="buyStockServiceTarget" class="footmark.spring.core.tx.declare.origin.BuyStockServiceImpl"> <property name="stockDao" ref="stockDao"/> </bean>
首先,咱們配置了一個 TransactionInterceptor 來定義相關的事務規則,它有兩個主要的屬性:一個是 transactionManager,用來指定一個事務管理器,並將具體事務相關的操做委託給它;另外一個是 Properties 類型的 transactionAttributes 屬性,它主要用來定義事務規則,該屬性的每個鍵值對中,鍵指定的是方法名,方法名可使用通配符,而值就表示相應方法的所應用的事務屬性。
指定事務屬性的取值有較複雜的規則,這在 Spring 中算得上是一件讓人頭疼的事。具體的書寫規則以下:
傳播行爲 [,隔離級別] [,只讀屬性] [,超時屬性] [不影響提交的異常] [,致使回滾的異常]
實例:
<property name="*Service"> PROPAGATION_REQUIRED,ISOLATION_READ_COMMITTED,TIMEOUT_20, +AbcException,+DefException,-HijException </property>
1)配置好了 TransactionInterceptor,咱們還須要配置一個 ProxyFactoryBean 來組裝 target 和advice。這也是典型的 Spring AOP 的作法。經過 ProxyFactoryBean 生成的代理類就是織入了事務管理邏輯後的目標類。
<bean id="buyStockService" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target" ref="buyStockServiceTarget"/> <property name="interceptorNames"> <list> <idref bean="transactionInterceptor"/> </list> </property> </bean>
2)除了使用上面的ProxyFactoryBean來組裝代理類表示哪些類須要使用到事務攔截器外,還可使用BeanNameAutoProxyCreator告訴Spring哪些類要使用事務攔截器進行攔截:
<!--指明事務攔截器攔截哪些類--> <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames"> <list> <value>*ServiceImpl</value> </list> </property> <property name="interceptorNames"> <list> <value>transactionInterceptor</value> </list> </property> </bean>
BeanName屬性告訴Spring如何攔截類。因爲聲明爲*ServiceImpl,全部關於Service是現實類都會被其攔截,而後interceptorNames則是定義事務攔截器,這樣對應的類和方法就會被事務管理器所攔截了。
至此,聲明式事務管理就算是實現了。咱們沒有對業務代碼進行任何操做,全部設置均在配置文件中完成,這就是聲明式事務的最大優勢。
前面的聲明式事務雖然好,可是卻存在一個很是惱人的問題:配置文件太多。咱們必須針對每個目標對象配置一個 ProxyFactoryBean;另外,雖然能夠經過父子 Bean 的方式來複用 TransactionInterceptor 的配置,可是實際的複用概率也不高;這樣,加上目標對象自己,每個業務類可能須要對應三個 <bean/>
配置,隨着業務類的增多,配置文件將會變得愈來愈龐大,管理配置文件又成了問題。
爲了緩解這個問題,Spring 爲咱們提供了 TransactionProxyFactoryBean,用於將TransactionInterceptor 和 ProxyFactoryBean 的配置合二爲一。
下面是每一個Bean都有一個代理的實現:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-aop-4.2.xsd "> <context:property-placeholder location="classpath:jdbc.properties"/> <!-- 註冊數據源 C3P0 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" > <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="jdbcUrl" value="${jdbc.url}"></property> <property name="user" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <bean id="accountDao" class="com.hrh.dao.impl.AccountDaoImpl"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="stockDao" class="com.hrh.dao.impl.StockDaoImpl"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="buyStockService" class="com.hrh.service.impl.BuyStockServiceImpl"> <property name="accountDao" ref="accountDao"></property> <property name="stockDao" ref="stockDao"></property> </bean> <!-- 事務管理器 --> <bean id="myTracnsactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 事務代理工廠 --> <!-- 生成事務代理對象 --> <bean id="serviceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager" ref="myTracnsactionManager"></property> <property name="target" ref="buyStockService"></property> <property name="transactionAttributes"> <props> <!-- 主要 key 是方法 ISOLATION_DEFAULT 事務的隔離級別 PROPAGATION_REQUIRED 傳播行爲 --> <prop key="add*">ISOLATION_DEFAULT,PROPAGATION_REQUIRED</prop> <!-- -Exception 表示發生指定異常回滾,+Exception 表示發生指定異常提交 --> <prop key="buyStock">ISOLATION_DEFAULT,PROPAGATION_REQUIRED,-BuyStockException</prop> </props> </property> </bean> </beans>
如此一來,配置文件與先前相比簡化了不少。咱們把這種配置方式稱爲 Spring 經典的聲明式事務管理。
可是,顯式爲每個業務類配置一個 TransactionProxyFactoryBean 的作法將使得代碼顯得過於刻板,爲此咱們可使用自動建立代理的方式來將其簡化(使用自動建立代理是純 AOP 知識)。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-aop-4.2.xsd "> <context:property-placeholder location="classpath:jdbc.properties"/> <!-- 註冊數據源 C3P0 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" > <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="jdbcUrl" value="${jdbc.url}"></property> <property name="user" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <bean id="accountDao" class="com.hrh.dao.impl.AccountDaoImpl"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="stockDao" class="com.hrh.dao.impl.StockDaoImpl"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="buyStockService" class="com.hrh.service.impl.BuyStockServiceImpl"> <property name="accountDao" ref="accountDao"></property> <property name="stockDao" ref="stockDao"></property> </bean> <!-- 事務管理器 --> <bean id="myTracnsactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="transactionBase" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" lazy-init="true" abstract="true"> <!-- 配置事務管理器 --> <property name="transactionManager" ref="myTracnsactionManager" /> <!-- 配置事務屬性 --> <property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> <!-- 共享基類 --> <bean id="serviceProxy" parent="transactionBase" > <property name="target" ref="buyStockService" /> </bean> </beans>
前面兩種聲明式事務配置方式奠基了 Spring 聲明式事務管理的基石。在此基礎上,Spring 2.x 引入了 <tx> 命名空間,結合使用 <aop> 命名空間,帶給開發人員配置聲明式事務的全新體驗,配置變得更加簡單和靈活。另外,得益於 <aop> 命名空間的切點表達式支持,聲明式事務也變得更增強大。
<!-- <tx:advice>定義事務通知,用於指定事務屬性,其中「transaction-manager」屬性指定事務管理器,並經過<tx:attributes>指定具體須要攔截的方法 <tx:method>攔截方法,其中參數有: name:方法名稱,將匹配的方法注入事務管理,可用通配符 propagation:事務傳播行爲, isolation:事務隔離級別定義;默認爲「DEFAULT」 timeout:事務超時時間設置,單位爲秒,默認-1,表示事務超時將依賴於底層事務系統; read-only:事務只讀設置,默認爲false,表示不是隻讀; rollback-for:須要觸發回滾的異常定義,可定義多個,以「,」分割,默認任何RuntimeException都將致使事務回滾,而任何Checked Exception將不致使事務回滾; no-rollback-for:不被觸發進行回滾的 Exception(s);可定義多個,以「,」分割; --> <tx:advice id="advice" transaction-manager="transactionManager"> <tx:attributes> <!-- 攔截save開頭的方法,事務傳播行爲爲:REQUIRED:必需要有事務, 若是沒有就在上下文建立一個 --> <tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED" timeout="" read-only="false" no-rollback-for="" rollback-for="java.lang.Exception"/> <!-- 支持,若是有就有,沒有就沒有 --> <tx:method name="*" propagation="SUPPORTS"/> </tx:attributes> </tx:advice> <!-- 定義切入點,expression爲切人點表達式,以下是指定impl包下的全部方法,具體以自身實際要求自定義 --> <aop:config> <aop:pointcut expression="execution(* com.hrh.*.service.impl.*.*(..))" id="pointcut"/> <!--<aop:advisor>定義切入點,與通知,把tx與aop的配置關聯,纔是完整的聲明事務配置 --> <aop:advisor advice-ref="advice" pointcut-ref="pointcut"/> </aop:config>
因爲使用了切點表達式,咱們就不須要針對每個業務類建立一個代理對象了。另外,若是配置的事務管理器 Bean 的名字取值爲 「transactionManager」,則咱們能夠省略 <tx:advice>
的 transaction-manager 屬性,由於該屬性的默認值即爲 「transactionManager」。
除了基於命名空間的事務配置方式,Spring 2.x 還引入了基於 Annotation 的方式,具體主要涉及@Transactional 標註。
xmlns:tx="http://www.springframework.org/schema/tx"
<!-- 開啓事務控制的註解支持 --> <tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="configLocation"> <value>classpath:mybatis-config.xml</value> </property> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>
使用@Transactional註解
@Transactional 能夠做用於接口、接口方法、類以及類方法上。看成用於類上時,該類的全部 public 方法都將具備該類型的事務屬性,同時,咱們也能夠在方法級別使用該註解來覆蓋類級別的定義。
雖然 @Transactional 註解能夠做用於接口、接口方法、類以及類方法上,可是 Spring 建議不要在接口或接口方法上使用該註解,由於這隻有在使用基於接口的代理時它纔會生效。另外,@Transactional 註解應該只被應用到 public 方法上,這是由 Spring AOP 的本質決定的。若是在 protected、private 或者默承認見性的方法上使用@Transactional註解,這將被忽略,也不會拋出任何異常。
@Transactional(timeout = 60)
基於 <tx>
命名空間和基於 @Transactional 的事務聲明方式各有優缺點。基於 <tx>
的方式,其優勢是與切點表達式結合,功能強大。利用切點表達式,一個配置能夠匹配多個方法,而基於 @Transactional 的方式必須在每個須要使用事務的方法或者類上用 @Transactional 標註,儘管可能大多數事務的規則是一致的,可是對 @Transactional 而言,也沒法重用,必須逐個指定。另外一方面,基於 @Transactional 的方式使用起來很是簡單明瞭,沒有學習成本。開發人員能夠根據須要,任選其中一種使用,甚至也能夠根據須要混合使用這兩種方式。
若是不是對遺留代碼進行維護,則不建議再使用基於 TransactionInterceptor 以及基於TransactionProxyFactoryBean 的聲明式事務管理方式。
<tx>
和 <aop>
命名空間的聲明式事務管理是目前推薦的方式,其最大特色是與 Spring AOP 結合緊密,能夠充分利用切點表達式的強大支持,使得管理事務更加靈活。