事務的第一個方面是傳播行爲(propagation behavior)。當事務方法被另外一個事務方法調用時,必須指定事務應該如何傳播。例如:方法可能繼續在現有事務中運行,也可能開啓一個新事務,並在本身的事務中運行。html
Spring定義了七種傳播行爲:java
注:如下具體講解傳播行爲的內容參考自Spring事務機制詳解mysql
此@Transactional註解來自org.springframework.transaction.annotation包,而不是javax.transaction。程序員
Spring事務管理的實現有許多細節,若是對整個接口框架有個大致瞭解會很是有利於咱們理解事務,下面經過講解Spring的事務接口來了解Spring實現事務的具體策略。
Spring事務管理涉及的接口的聯繫以下:spring
Spring並不直接管理事務,而是提供了多種事務管理器,他們將事務管理的職責委託給Hibernate或者JTA等持久化機制所提供的相關平臺框架的事務來實現。
Spring事務管理器的接口是org.springframework.transaction.PlatformTransactionManager,經過這個接口,Spring爲各個平臺如JDBC、Hibernate等都提供了對應的事務管理器,可是具體的實現就是各個平臺本身的事情了。此接口的內容以下:sql
Public interface PlatformTransactionManager()...{ // 由TransactionDefinition獲得TransactionStatus對象 TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; // 提交 Void commit(TransactionStatus status) throws TransactionException; // 回滾 Void rollback(TransactionStatus status) throws TransactionException; }
從這裏可知具體的具體的事務管理機制對Spring來講是透明的,它並不關心那些,那些是對應各個平臺須要關心的,因此Spring事務管理的一個優勢就是爲不一樣的事務API提供一致的編程模型,如JTA、JDBC、Hibernate、JPA。下面分別介紹各個平臺框架實現事務管理的機制。數據庫
若是應用程序中直接使用JDBC來進行持久化,DataSourceTransactionManager會爲你處理事務邊界。爲了使用DataSourceTransactionManager,你須要使用以下的XML將其裝配到應用程序的上下文定義中:編程
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>
實際上,DataSourceTransactionManager是經過調用java.sql.Connection來管理事務,然後者是經過DataSource獲取到的。經過調用鏈接的commit()方法來提交事務,一樣,事務失敗則經過調用rollback()方法進行回滾。session
若是應用程序的持久化是經過Hibernate實習的,那麼你須要使用HibernateTransactionManager。對於Hibernate3,須要在Spring上下文定義中添加以下的<bean>聲明:併發
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean>
sessionFactory屬性須要裝配一個Hibernate的session工廠,HibernateTransactionManager的實現細節是它將事務管理的職責委託給org.hibernate.Transaction對象,然後者是從Hibernate Session中獲取到的。當事務成功完成時,HibernateTransactionManager將會調用Transaction對象的commit()方法,反之,將會調用rollback()方法。
Hibernate多年來一直是事實上的Java持久化標準,可是如今Java持久化API做爲真正的Java持久化標準進入你們的視野。若是你計劃使用JPA的話,那你須要使用Spring的JpaTransactionManager來處理事務。你須要在Spring中這樣配置JpaTransactionManager:
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean>
JpaTransactionManager只須要裝配一個JPA實體管理工廠(javax.persistence.EntityManagerFactory接口的任意實現)。JpaTransactionManager將與由工廠所產生的JPA EntityManager合做來構建事務。
若是你沒有使用以上所述的事務管理,或者是跨越了多個事務管理源(好比兩個或者是多個不一樣的數據源),你就須要使用JtaTransactionManager:
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"> <property name="transactionManagerName" value="java:/TransactionManager" /> </bean>
JtaTransactionManager將事務管理的責任委託給javax.transaction.UserTransaction和javax.transaction.TransactionManager對象,其中事務成功完成經過UserTransaction.commit()方法提交,事務失敗經過UserTransaction.rollback()方法回滾。
上面講到的事務管理器接口PlatformTransactionManager經過getTransaction(TransactionDefinition definition)方法來獲得事務,這個方法裏面的參數是TransactionDefinition類,這個類就定義了一些基本的事務屬性。
那麼什麼是事務屬性呢?事務屬性能夠理解成事務的一些基本配置,描述了事務策略如何應用到方法上。事務屬性包含了5個方面,如圖所示:
而TransactionDefinition接口內容以下:
public interface TransactionDefinition { // 返回事務的傳播行爲 int getPropagationBehavior(); // 返回事務的隔離級別,事務管理器根據它來控制另一個事務能夠看到本事務內的哪些數據 int getIsolationLevel(); // 返回事務必須在多少秒內完成 int getTimeout(); // 事務是否只讀,事務管理器可以根據這個返回值進行優化,確保事務是隻讀的 boolean isReadOnly(); }
咱們能夠發現TransactionDefinition正好用來定義事務屬性,下面詳細介紹一下各個事務屬性。
在應用系統調用 事務聲明 的目標方法時,Spring Framework 默認使用 AOP 代理,在代碼運行時生成一個代理對象,根據事務配置信息,這個代理對象決定該聲明事務的目標方法是否由攔截器 TransactionInterceptor 來使用攔截,在 TransactionInterceptor 攔截時,會在目標方法開始執行以前建立並加入事務,並執行目標方法的邏輯, 最後根據執行狀況是否出現異常,利用抽象事務管理器 AbstractPlatformTransactionManager 操做數據源 DataSource 提交或回滾事務。
Spring AOP 代理有 CglibAopProxy 和 JdkDynamicAopProxy 兩種,以CglibAopProxy 爲例,對於CglibAopProxy,須要調用其內部類的DynamicAdvisedInterceptor 的 intercept 方法。對於 JdkDynamicAopProxy,則調用其 invoke 方法。
事務失效問題
在同一個代理對象內部,事務方法之間的直接嵌套調用,普通方法和事務方法之間的直接嵌套調用,都會形成事務異常!具體表現爲某些傳播行爲不生效或者直接事務控制不生效。
@Service public class DemoService { @Transaction public void transactionMethod1() { op1(); op2(); ... } public void commonMethod(){ ... transactionMethod1(); //照顧基礎不牢的朋友,這裏至關於 this.transactionMethod1(); ... } @Transaction public void transactionMethod2(){ ... this.transactionMethod3(); ... } @Transaction(propagation= Propagation.REQUIRES_NEW) public void transactionMethod3() { op3(); ... } }
上面代碼中,若是調用 DemoService 的 bean 對象的commonMethod ,則transactionMethod1裏定義的事務將不生效(好比op2發生錯誤時,並不會回滾op1的操做),bean 調用 transactionMethod2時,transactionMethod2時裏面調用的transactionMethod3也不會開啓新的事務。
爲何會這樣?
上面的實現機制中講到,AOP的實現都是經過動態代理來實現,而AOP限制了咱們只能在目標方法的開始和結束做爲切點作切入處理加強。當動態代理對象最終調用的原始對象的目標方法時,並不能干預到目標方法內的方法調用行爲,若是原始對象直接調用(用this.xxx方式)其餘同類方法時,實際調用的是原始對象自身的方法,而不是代理類對象加強後(增長事務控制後)的方法。此時Spring對方法事務的控制(包括事務的傳播行爲、事務的隔離級別等)徹底失效。
如何解決?
要想解決此類問題,主要都在於原始對象在調用對象內其餘方法時,不要使用this.xxx的方式直接調用,經過注入或者獲取代理對象的方式,使用代理對象調用須要調用的方法。下面列舉幾個解決方式:
1.注入自身,使用代理對象調用
@Service public class DemoService { @Autowired DemoService demoService; @Transaction public void transactionMethod1() { op1(); op2(); ... } public void commonMethod() { ... //this.transactionMethod1() -> demoService.transactionMethod1() demoService.transactionMethod1(); ... } }
2.使用AopContext,獲取當前代理對象
@Service public class DemoService { @Transaction public void transactionMethod1() { op1(); op2(); ... } public void commonMethod() { ... //this.transactionMethod1() -> ((DemoService)AopContext.currentProxy()).transactionMethod1();) ((DemoService)AopContext.currentProxy()).transactionMethod1(); ... } ... }
3.使用BeanFactory獲取代理對象(代碼略)