在spring中使用聲明型事務

在spring中使用聲明型事務
spring使用aop機制管理jdbc的連接和事務。它使用TransactionInterceptor類,Spring事務支持中的核心接口是
org.springframework.transaction.PlatformTransactionManager。 爲了實際執行事務,Spring所有的事務劃分功能都通過傳遞適當的TransactionDefinition實例,委託給 PlatformTransactionManager。
儘管PlatformTransactionManager接口可以直接使用,應用程序通常配置具體的事務管理器並使用聲明性事務來劃分事務。
Spring具有多種PlatformTransactionManager實現,它們分爲兩類:
局 部事務策略即針對單個資源執行事務(主要是針對單個的數據庫)。實現有 org.springframework.jdbc.datasource.DataSourceTransactionManager。 它用於jdbc數據源的配置,調用TransactionInterceptor開是一個事務,
從DataSource得到一個connection並確保auto-commit設爲disabled。他用JdbcTemplate在一個線程內綁定一個JDBC connection,TransactionInterceptor負責提交事務,
DataSourceTransactionManager 調用Connection.commit()關閉connection,並解除綁定(potentially allowing for one thread connection per data source)。
例如
<bean id="DataSource"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName">
<value>oracle.jdbc.driver.OracleDriver</value>
</property>
<property name="url">
<value>jdbc:oracle:thin:@localhost:1521:hua2</value>
</property>
<property name="username">
<value>user</value></property>
<property name="password">
<value>gotpassword</value>
</property>
</bean>
</beans>
<bean id="DataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="DataSource" />
</bean>
<bean id="tatanTransactionScriptsProxyCreator"
class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<idref bean="tatanTransactionScripts" />
</list>
</property>
<property name="interceptorNames">
<list>
<idref bean="DataSourceTransactionInterceptor" />
</list>
</property>
</bean>
<bean id="DataSourceTransactionInterceptor"
class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager"
ref="DataSourceTransactionManager" />
<property name="transactionAttributeSource">
<value>
com.tatan.tatanTransactionScriptsImpl.*=PROPAGATION_REQUIRED
</value>
</property>
</bean>
 transactionAttributesSource 屬性指定每個方法的transaction attribute,PROPAGATION_REQUIRED說明在一個事務內這個方法被執行。
 和EJB一樣,默認的情況下,spring只有當unchecked exception被拋出時,才rollback事務,也可以自己加入checked exception。
 tatanTransactionScripts被TransactionInterceptor封裝,在一個事物內執行類的每一個方法。
更爲簡單的配置
 
    <bean id="UserManagerTran"
            class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
            <property name="transactionManager">
                <ref bean="transactionManager"/></property>
            <property name="target"><ref bean="UserManager"/></property>
            <property name="transactionAttributes">
                <props>
                    <prop key="insert*">
                        PROPAGATION_REQUIRED, ISOLATION_READ_COMMITTED</prop>
                    <prop key="tran*">
                        PROPAGATION_REQUIRED, ISOLATION_SERIALIZABLE</prop>
                    <prop key="deposit*">
                        PROPAGATION_REQUIRED, ISOLATION_READ_COMMITTED</prop>
                </props>
            </property>
        </bean>
         <bean id="transactionManager"
           class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
           <property name="dataSource"><ref bean="dataSource"/></property>
       </bean>

(The TransactionProxyFactoryBean is a ProxyFactoryBean where every bean is adviced with a TransactionInterceptor. And the TransactionInterceptor is a piece of advice.
So you can use a seperate TransactionInterceptor and ProxyFactoryBean. But if you are lazy/smart, you can use the TransactionProxyFactoryBean that does the same thing (only less configuration needed))
對於特定的方法或方法命名模式,代理的具體事務行爲由事務屬性驅動,如下面的例子所示:
<prop key="insert*">
 ROPAGATION_REQUIRED, ISOLATION_READ_COMMITTED
 </prop>
key屬性確定代理應該給哪個方法增加事務行爲。這樣的屬性最重要的部份是傳播行爲。有以下選項可供使用:
 PROPAGATION_REQUIRED--支持當前事務,如果當前沒有事務,就新建一個事務。這是最常見的選擇。
 PROPAGATION_SUPPORTS--支持當前事務,如果當前沒有事務,就以非事務方式執行。
 PROPAGATION_MANDATORY--支持當前事務,如果當前沒有事務,就拋出異常。
 PROPAGATION_REQUIRES_NEW--新建事務,如果當前存在事務,把當前事務掛起。
 PROPAGATION_NOT_SUPPORTED--以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
 PROPAGATION_NEVER--以非事務方式執行,如果當前存在事務,則拋出異常。
 PROPAGATION_NESTED--如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則進行與PROPAGATION_REQUIRED類似的操作。
 前六個策略類似於EJB CMT,第七個(PROPAGATION_NESTED)是Spring所提供的一個特殊變量。
 它要求事務管理器或者使用JDBC 3.0 Savepoint API提供嵌套事務行爲(如Spring的DataSourceTransactionManager)。
事 務屬性中的readOnly標誌表示對應的事務應該被最優化爲只讀事務。這是一個最優化提示。在一些情況下,一些事務策略能夠起到顯著的最優化效果,例如 在使用Object/Relational映射工具(如:Hibernate或TopLink)時避免dirty checking(試圖“刷新”)。
在事務屬性中還有定義“timeout”值的選項,指定事務超時爲幾秒。在JTA中,這將被簡單地傳遞到J2EE服務器的事務協調程序,並據此得到相應的解釋。

   全局事務管理即執行有可能跨越多個資源的全局事務。主要對應的Spring類是org.springframework.transaction.jta.JtaTransactionManager,它委託給遵循JTA規範的J2EE服務器,也有例外。
spring支持JTA,只需要一個標準的JtaTransactionManager定義,數據庫必須支持XA protocol,或者J2EE服務器提供支持XA規範的DataSource。
默 認的Spring JtaTransactionManager設置將從標準的JNDI位置獲取JTA的 javax.transaction.UserTransaction對象,該JNDI位置由J2EE指 定:java:comp/UserTransaction。對於大多數標準J2EE環境下的用例來說,它工作良好。
但是,默認的 JtaTransactionManager不能執行事務掛起操作(即它不支持PROPAGATION_REQUIRES_NEW和 PROPAGATION_NOT_SUPPORTED)。原因是標準的JTA UserTransaction接口不支持掛起或恢復事務的操作;它只支持開始和完成新事務的操作。
爲執行事務掛起操作,還需要提供javax.transaction.TransactionManager實例,按照JTA的規定,它提供標準的掛起和恢復方法。遺憾的是,J2EE沒有爲JTA TransactionManager定義標準的JNDI位置!
因此,必須使用特定於供應商的(vendor-specific)查尋機制。J2EE沒有考慮把JTA TransactionManager接口作爲它的公開API的一部分。JTA規範規定的TransactionManager接口原本是打算用於容器集成的。
但是爲JTA TransactionManager定義標準的JNDI位置還是有重大意義的,尤其是對於輕量級容器(如Spring);然後,便可以以同樣的方式來定位任意的J2EE服務器的JTA TransactionManager。
結合jboss JTA的Spring事務劃分
oracle-ds.xml
<?xml version="1.0" encoding="UTF-8"?>

<datasources>
 <xa-datasource>
  <jndi-name>XASpringDS</jndi-name>
  <track-connection-by-tx/>
  <isSameRM-override-value>false</isSameRM-override-value>
  <xa-datasource-class>oracle.jdbc.xa.client.OracleXADataSource</xa-datasource-class>
  <xa-datasource-property name="URL">jdbc:oracle:oci8:@orcl</xa-datasource-property>
  <xa-datasource-property name="User">SCOTT</xa-datasource-property>
  <xa-datasource-property name="Password">tiger</xa-datasource-property>
  <exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.OracleExceptionSorter</exception-sorter-class-name>
  <no-tx-separate-pools/>
 </xa-datasource>
 <mbean
  code="org.jboss.resource.adapter.jdbc.xa.oracle.OracleXAExceptionFormatter"
  name="jboss.jca:service=OracleXAExceptionFormatter">
  <depends optional-attribute-name="TransactionManagerService">
   jboss:service=TransactionManager</depends>
 </mbean>
</datasources>
spring配置
<!-- Data source bean -->
 
 <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
  <property name="jndiName"><value>java:/XASpringDS</value></property>
 </bean>
 
 <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>

Spring有效地將DAO實現從實際的運行時環境中分離出來,允許在J2EE容器之外輕鬆地測試或重用用。
Spring提供了多種事務策略,比如JtaTransactionManager和JDBC DataSourceTransactionManager,
前者委託給J2EE服務器的事務協調程序,後者則針對單個JDBC DataSource(即單個的目標數據庫)執行事務。
通過對後端配置進行簡單的更改,就能夠輕鬆地調整事務策略適應另一個環境。

http://blogger.org.cn/blog/more.asp?name=hongrui&id=11162

Spring事務傳播機制解惑

概述

當我們調用一個基於Spring的Service接口方法(如UserService#addUser())時,它將運行於Spring管理的事務 環境中,Service接口方法可能會在內部調用其它的Service接口方法以共同完成一個完整的業務操作,因此就會產生服務接口方法嵌套調用的情 況,Spring通過事務傳播行爲控制當前的事務如何傳播到被嵌套調用的目標服務接口方法中。

事務傳播是Spring進行事務管理的重要概念,其重要性怎麼強調都不爲過。但是事務傳播行爲也是被誤解最多的地方,在本文裏,我們將詳細分析不同事務傳播行爲的表現形式,掌握它們之間的區別。

事務傳播行爲種類

Spring在TransactionDefinition接口中規定了7種類型的事務傳播行爲,它們規定了事務方法和事務方法發生嵌套調用時事務如何進行傳播:

1事務傳播行爲類型

事務傳播行爲類型

說明

PROPAGATION_REQUIRED

如果當前沒有事務,就新建一個事務,如果已經存在一個事務中,加入到這個事務中。這是最常見的選擇。

PROPAGATION_SUPPORTS

支持當前事務,如果當前沒有事務,就以非事務方式執行。

PROPAGATION_MANDATORY

使用當前的事務,如果當前沒有事務,就拋出異常。

PROPAGATION_REQUIRES_NEW

新建事務,如果當前存在事務,把當前事務掛起。

PROPAGATION_NOT_SUPPORTED

以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。

PROPAGATION_NEVER

以非事務方式執行,如果當前存在事務,則拋出異常。

PROPAGATION_NESTED

如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則執行與PROPAGATION_REQUIRED類似的操作。

當使用PROPAGATION_NESTED時,底層的數據源必須基於JDBC 3.0,並且實現者需要支持保存點事務機制。

幾種容易引起誤解的組合事務傳播行爲

當服務接口方法分別使用表1中不同的事務傳播行爲,且這些接口方法又發生相互調用的情況下,大部分組合都是一目瞭然,容易理解的。但是,也存在一些容易引起誤解的組合事務傳播方式。

下面,我們通過兩個具體的服務接口的組合調用行爲來破解這一難點。這兩個服務接口分別是UserService和 ForumService,UserSerice有一個addCredits()方法,ForumSerivce#addTopic()方法調用了 UserSerice#addCredits()方法,發生關聯性服務方法的調用:

public class ForumService {

private UserService userService;

public void addTopic() {①調用其它服務接口的方法

//add Topic…

userService.addCredits() ;②被關聯調用的業務方法

}

}

嵌套調用的事務方法

對Spring事務傳播行爲最常見的一個誤解是:當服務接口方法發生嵌套調用時,被調用的服務方法只能聲明爲 PROPAGATION_NESTED。這種觀點犯了望文生義的錯誤,誤認爲PROPAGATION_NESTED是專爲方法嵌套準備的。這種誤解遺害不 淺,執有這種誤解的開發者錯誤地認爲:應儘量不讓Service類的業務方法發生相互的調用,Service類只能調用DAO層的DAO類,以避免產生嵌 套事務。

其實,這種顧慮是完全沒有必要的,PROPAGATION_REQUIRED已經清楚地告訴我們:事務的方法會足夠“聰明”地判斷上下文是否已經存在一個事務中,如果已經存在,就加入到這個事務中,否則創建一個新的事務。

依照上面的例子,假設我們將ForumService#addTopic()和UserSerice#addCredits()方法的事務傳播行爲都設置爲PROPAGATION_REQUIRED,這兩個方法將運行於同一個事務中。

爲了清楚地說明這點,可以將Log4J的日誌設置爲DEBUG級別,以觀察Spring事務管理器內部的運行情況。下面將兩個業務方法都設置爲PROPAGATION_REQUIRED,Spring所輸出的日誌信息如下:

Using transaction object

[org.springframework.jdbc.datasource.DataSourceTransactionManager$DataSourceTransactionObject@e3849c]

①爲 ForumService#addTopic() 新建一個事務

Creating new transaction with name [com.baobaotao.service.ForumService.addTopic]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT

Acquired Connection [org.apache.commons.dbcp.PoolableConnection@dc41c5] for JDBC transaction

Switching JDBC Connection [org.apache.commons.dbcp.PoolableConnection@dc41c5] to manual commit

Bound value [org.springframework.jdbc.datasource.ConnectionHolder@ee1ede] for key [org.apache.commons.dbcp.BasicDataSource@4204] to thread [main]

Initializing transaction synchronization

Getting transaction for [com.baobaotao.service.ForumService.addTopic]

Retrieved value [org.springframework.jdbc.datasource.ConnectionHolder@ee1ede] for key [org.apache.commons.dbcp.BasicDataSource@4204] bound to thread [main]

Using transaction object [org.springframework.jdbc.datasource.DataSourceTransactionManager$DataSourceTransactionObject@8b8a47]

UserService#addCredits() 簡單地加入到已存在的事務中(即①處創建的事務)

Participating in existing transaction

Getting transaction for [com.baobaotao.service.UserService.addCredits]

Completing transaction for [com.baobaotao.service.UserService.addCredits]

Completing transaction for [com.baobaotao.service.ForumService.addTopic]

Triggering beforeCommit synchronization

Triggering beforeCompletion synchronization

Initiating transaction commit

③調用底層 Connection#commit() 方法提交事務

Committing JDBC transaction on Connection [org.apache.commons.dbcp.PoolableConnection@dc41c5]

Triggering afterCommit synchronization

Triggering afterCompletion synchronization

Clearing transaction synchronization

嵌套事務

將ForumService#addTopic()設置爲PROPAGATION_REQUIRED 時,UserSerice#addCredits()設置爲PROPAGATION_REQUIRED、PROPAGATION_SUPPORTS、 PROPAGATION_MANDATORY時,運行的效果都是一致的(當然,如果單獨調用addCredits()就另當別論了)。

當addTopic()運行在一個事務下(如設置爲PROPAGATION_REQUIRED),而addCredits()設置爲 PROPAGATION_NESTED時,如果底層數據源支持保存點,Spring將爲內部的addCredits()方法產生的一個內嵌的事務。如果 addCredits()對應的內嵌事務執行失敗,事務將回滾到addCredits()方法執行前的點,並不會將整個事務回滾。內嵌事務是內層事務的一 部分,所以只有外層事務提交時,嵌套事務才能一併提交。

嵌套事務不能夠提交,它必須通過外層事務來完成提交的動作,外層事務的回滾也會造成內部事務的回滾。

嵌套事務和新事務

PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED也是容易混淆的兩個傳播行爲。PROPAGATION_REQUIRES_NEW 啓動一個新的、和外層事務無關的“內部”事務。該事務擁有自己的獨立隔離級別和鎖,不依賴於外部事務,獨立地提交和回滾。當內部事務開始執行時,外部事務 將被掛起,內務事務結束時,外部事務才繼續執行。

由此可見, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大區別在於:PROPAGATION_REQUIRES_NEW 將創建一個全新的事務,它和外層事務沒有任何關係,而 PROPAGATION_NESTED 將創建一個依賴於外層事務的子事務,當外層事務提交或回滾時,子事務也會連帶提交和回滾。

其它需要注意問題

以下幾個問題值得注意:

1) 當業務方法被設置爲PROPAGATION_MANDATORY時,它就不能被非事務的業務方法調用。如將 ForumService#addTopic()設置爲PROPAGATION_MANDATORY,如果展現層的Action直接調用 addTopic()方法,將引發一個異常。正確的情況是:addTopic()方法必須被另一個帶事務的業務方法調用(如 ForumService#otherMethod())。所以PROPAGATION_MANDATORY的方法一般都是被其它業務方法間接調用的。

2) 當業務方法被設置爲PROPAGATION_NEVER時,它將不能被擁有事務的其它業務方法調用。假設 UserService#addCredits()設置爲PROPAGATION_NEVER,當ForumService# addTopic()擁有一個事務時,addCredits()方法將拋出異常。所以PROPAGATION_NEVER方法一般是被直接調用的。

3)當方法被設置爲PROPAGATION_NOT_SUPPORTED時,外層業務方法的事務會被掛起,當內部方法運行完成後,外層方法的事務重新運行。如果外層方法沒有事務,直接運行,不需要做任何其它的事。

小結

在Spring聲明式事務管理的配置中,事務傳播行爲是最容易被誤解的配置項,原因在於事務傳播行爲名稱(如 PROPAGATION_NESTED:嵌套式事務)和代碼結構的類似性上(業務類方法嵌套調用另一個業務類方法)。這種誤解在很多Spring開發者中 廣泛存在,本文深入講解了Spring事務傳播行爲對業務方法嵌套調用的真實影響,希望能幫助讀者化解對事務傳播行爲的困惑。

============================================

Spring 聲明式事務管理源碼解讀之事務提交

簡介: 上次說到spring 聲明式事務管理的事務開始部分,按流程來講,下面應該提交事務了, spring 的聲明式事務管理其實是比較複雜的,事實上這種複雜性正是由於事務本身的複雜性導致的,如果能用兩三句話就把這部分內容說清楚是不現實的,也是不成熟的,而我對這部分的理解也可能是不全面的,還是那句話,希望大家和我一起把本貼的質量提交起來。
在下面的文章中,我講會多次提到第一篇文章,第一篇文章的地址是:http://www.javaeye.com/topic/87426
如果要理解事務提交的話,理解事務開始是一個前提條件,所以請先看第一篇文章,再來看這篇
如果你仔細看下去,我想肯定是有很多收穫,因爲我們確實能從spring 的代碼和思想中學到很多東西。

正文:

其實俺的感覺就是事務提交要比事務開始複雜,看事務是否提交我們還是要回到TransactionInterceptor類的invoke方法

代碼
  1. public Object invoke(MethodInvocation invocation) throws Throwable {  
  2. // Work out the target class: may be < code > null </ code > .  
  3. // The TransactionAttributeSource should be passed the target class  
  4. // as well as the method, which may be from an interface  
  5. Class targetClass  = (invocation.getThis() != null) ? invocation.getThis().getClass() : null;  
  6. // Create transaction if necessary.  
  7. TransactionInfo txInfo  =  createTransactionIfNecessary (invocation.getMethod(), targetClass);  
  8. Object retVal  =  null ;  
  9. try {  
  10. // This is an around advice.  
  11. // Invoke the next interceptor in the chain.  
  12. // This will normally result in a target object being invoked.  
  13. retVal  =  invocation .proceed();  
  14. }  
  15. catch (Throwable ex) {  
  16. // target invocation exception  
  17. doCloseTransactionAfterThrowing(txInfo, ex);  
  18. throw ex;  
  19. }  
  20. finally {  
  21. doFinally(txInfo);//業務方法出棧後必須先執行的一個方法  
  22. }  
  23. doCommitTransactionAfterReturning(txInfo);  
  24. return retVal;  
  25. }  
  1. public Object invoke(MethodInvocation invocation) throws Throwable {  
  2. // Work out the target class: may be < code > null </ code > .  
  3. // The TransactionAttributeSource should be passed the target class  
  4. // as well as the method, which may be from an interface  
  5. Class targetClass  = (invocation.getThis() != null) ? invocation.getThis().getClass() : null;  
  6. // Create transaction if necessary.  
  7. TransactionInfo txInfo  =  createTransactionIfNecessary (invocation.getMethod(), targetClass);  
  8. Object retVal  =  null ;  
  9. try {  
  10. // This is an around advice.  
  11. // Invoke the next interceptor in the chain.  
  12. // This will normally result in a target object being invoked.  
  13. retVal  =  invocation .proceed();  
  14. }  
  15. catch (Throwable ex) {  
  16. // target invocation exception  
  17. doCloseTransactionAfterThrowing(txInfo, ex);  
  18. throw ex;  
  19. }  
  20. finally {  
  21. doFinally(txInfo);//業務方法出棧後必須先執行的一個方法  
  22. }  
  23. doCommitTransactionAfterReturning(txInfo);  
  24. return retVal;  
  25. }  


其中的doFinally(txInfo)那一行很重要,也就是說不管如何,這個doFinally方法都是要被調用的,爲什麼它這麼重要呢,舉個例子:
我們還是以propregation_ required 來舉例子吧,假設情況是這樣的,AService中有一個方法調用了BService中的,這兩個方法都處在事務體之中,他們的傳播途徑都是required 。 那麼調用開始了,AService的方法首先入方法棧,並創建了TransactionInfo的實例,接着BService的方法入棧,又創建了一個 TransactionInfo的實例,而重點要說明的是TransactionInfo是一個自身關聯的內部類,第二個方法入棧時,會給新創建的 TransactionInfo的實例設置一個屬性,就是TransactionInfo對象中的private TransactionInfo oldTransactionInfo;屬性,這個屬性表明BService方法的創建的TransactionInfo對象是有一個old的 transactionInfo對象的,這個oldTransactionInfo對象就是AService方法入棧時創建的 TransactionInfo對象,我們還記得在createTransactionIfNecessary方法裏有這樣一個方法吧:

代碼
  1. protected  TransactionInfo createTransactionIfNecessary(Method method, Class targetClass) {  
  2. // We always bind the TransactionInfo to the thread, even if we didn't create   
  3. // a new transaction here. This guarantees that the TransactionInfo stack   
  4. // will be managed correctly even if no transaction was created by this aspect.   
  5. txInfo.bindToThread();  
  6. return  txInfo;  
  7. }  
  8. 就是這個bindToThread()方法在作怪:  
  9. private   void  bindToThread() {  
  10. // Expose current TransactionStatus, preserving any existing transactionStatus for   
  11. // restoration after this transaction is complete.   
  12. oldTransactionInfo = (TransactionInfo) currentTransactionInfo.get();  
  13. currentTransactionInfo.set(this );  
  14. }  
  1. protected  TransactionInfo createTransactionIfNecessary(Method method, Class targetClass) {  
  2. // We always bind the TransactionInfo to the thread, even if we didn't create   
  3. // a new transaction here. This guarantees that the TransactionInfo stack   
  4. // will be managed correctly even if no transaction was created by this aspect.   
  5. txInfo.bindToThread();  
  6. return  txInfo;  
  7. }  
  8. 就是這個bindToThread()方法在作怪:  
  9. private   void  bindToThread() {  
  10. // Expose current TransactionStatus, preserving any existing transactionStatus for   
  11. // restoration after this transaction is complete.   
  12. oldTransactionInfo = (TransactionInfo) currentTransactionInfo.get();  
  13. currentTransactionInfo.set(this );  
  14. }  


如果當前線程中已經有了一個TransactionInfo,則拿出來放到新建的transactionInfo對象的oldTransactionInfo屬性中,然後再把新建的TransactionInfo設置到當前線程中。

這裏有一個概念要搞清楚,就是TransactionInfo對象並不是表明事務狀態的對象,表明事務狀態的對象是TransactionStatus對象,這個對象同樣是TransactionInfo的一個屬性(這一點,我在前面一篇文章中並沒有講清楚)。

接下來BService中的那個方法返回,那麼該它退棧了,它退棧後要做的就是doFinally方法,即把它的 oldTransactionInfo設置到當前線程中(這個TransactionInfo對象顯然就是AService方法入棧時創建的,怎麼現在又 要設置到線程中去呢,原因就是BService的方法出棧時並不提交事務,因爲BService的傳播途徑是required , 所以要把棧頂的方法所創建transactioninfo給設置到當前線程中),即調用AService的方法時所創建的TransactionInfo 對象。那麼在AServie的方法出棧時同樣會設置TransactionInfo對象的oldTransactionInfo到當前線程,這時候顯然 oldTransactionInfo是空的,但AService中的方法會提交事務,所以它的oldTransactionInfo也應該是空了。

在這個小插曲之後,麼接下來就應該是到提交事務了,之前在AService的方法出棧時,我們拿到了它入棧時創建的TransactionInfo 對象,這個對象中包含了AService的方法事務狀態。即TransactionStatus對象,很顯然,太顯然了,事務提交中的任何屬性都和事務開 始時的創建的對象息息相關,這個TransactionStatus對象哪裏來的,我們再回頭看看createTransactionIfNessary 方法吧:

代碼
  1. protected  TransactionInfo createTransactionIfNecessary(Method method, Class targetClass) {  
  2. txInfo.newTransactionStatus(this .transactionManager.getTransaction(txAttr));  
  3. }  
  1. protected  TransactionInfo createTransactionIfNecessary(Method method, Class targetClass) {  
  2. txInfo.newTransactionStatus(this .transactionManager.getTransaction(txAttr));  
  3. }  


再看看transactionManager.getTransaction(txAttr)方法吧:

代碼
  1. public   final  TransactionStatus getTransaction(TransactionDefinition definition)  throws  TransactionException {  
  2. else   if  (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||  
  3. definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||  
  4. definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {  
  5. if  (debugEnabled) {  
  6. logger.debug("Creating new transaction with name ["  + definition.getName() +  "]" );  
  7. }  
  8. doBegin(transaction, definition);  
  9. boolean  newSynchronization = ( this .transactionSynchronization != SYNCHRONIZATION_NEVER);  
  10. return  newTransactionStatus(definition, transaction,  true , newSynchronization, debugEnabled,  null ); //注意這裏的返回值,返回的就是一個TransactionStatus對象,這個對象表明了一個事務的狀態,比如說是否是一個新的事務,事務是否已經結束,等等,這個對象是非常重要的,在事務提交的時候還是會用到它的。        }   
  11. }  
  12. }  
  1. public   
  2. final  TransactionStatus getTransaction(TransactionDefinition  
  3. definition) throws  TransactionException {  
  4. else   if  (definition.getPropagationBehavior() ==  
  5. TransactionDefinition.PROPAGATION_REQUIRED ||  
  6. definition.getPropagationBehavior() ==  
  7. TransactionDefinition.PROPAGATION_REQUIRES_NEW ||  
  8. definition.getPropagationBehavior() ==  
  9. TransactionDefinition.PROPAGATION_NESTED) {  
  10. if  (debugEnabled) {  
  11. logger.debug("Creating new transaction with name ["  +  
  12. definition.getName() + "]" );  
  13. }  
  14. doBegin(transaction, definition);  
  15. boolean  newSynchronization = ( this .transactionSynchronization !=  
  16. SYNCHRONIZATION_NEVER);  
  17. return  newTransactionStatus(definition, transaction,  true ,  
  18. newSynchronization, debugEnabled,  
  19. null ); //注意這裏的返回值,返回的就是一個TransactionStatus對象,這個對象表明了一個事務的狀態,比如說是否是一個新的事務,   
  20. 事務是否已經結束,等等,這個對象是非常重要的,在事務提交的時候還是會用到它的。 }  
  21. }  
  22. }  


還有一點需要說明的是,AService的方法在執行之前創建的transactionstatus確實是通過這個方法創建的,但是,BService的方法在執行之前創建transactionstatus的方法就與這個不一樣了,下面會有詳解。

回顧了事務開始時所調用的方法之後,是不是覺得現在對spring 如何處理事務越來越清晰了呢。由於這麼幾個方法的調用,每個方法入棧之前它的事務狀態就已經被設置好了。這個事務狀態就是爲了在方法出棧時被調用而準備的。

讓我們再次回到BService中的方法出棧的那個時間段,看看spring 都 做了些什麼,我們知道,後入棧的肯定是先出棧,BService中的方法後入棧,拿它肯定要先出棧了,它出棧的時候是要判斷是否要提交事務,釋放資源的, 讓我們來看看TransactionInterceptor的invoke的最後那個方法 doCommitTransactionAfterReturning:

代碼
  1. protected   void  doCommitTransactionAfterReturning(TransactionInfo txInfo) {  
  2. if  (txInfo !=  null  && txInfo.hasTransaction()) {  
  3. if  (logger.isDebugEnabled()) {  
  4. logger.debug("Invoking commit for transaction on "  + txInfo.joinpointIdentification());  
  5. }  
  6. this .transactionManager.commit(txInfo.getTransactionStatus());  
  7. //瞧:提交事務時用到了表明事務狀態的那個TransactionStatus對象了。   
  8. }  
  9. }  
  1. protected   void  doCommitTransactionAfterReturning(TransactionInfo txInfo) {  
  2. if  (txInfo !=  null  && txInfo.hasTransaction()) {  
  3. if  (logger.isDebugEnabled()) {  
  4. logger.debug("Invoking commit for transaction on "  + txInfo.joinpointIdentification());  
  5. }  
  6. this .transactionManager.commit(txInfo.getTransactionStatus());  
  7. //瞧:提交事務時用到了表明事務狀態的那個TransactionStatus對象了。   
  8. }  
  9. }  


看這個方法的名字就知道spring 是要在業務方法出棧時提交事務,貌似很簡單,但是事實是這樣的嗎? 我們接着往下看。

代碼
  1. public   final   void  commit(TransactionStatus status)  throws  TransactionException {  
  2. DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;  
  3. if  (defStatus.isCompleted()) {  
  4. throw   new  IllegalTransactionStateException(  
  5. "Transaction is already completed - do not call commit or rollback more than once per transaction" );  
  6. }  
  7. if  (defStatus.isLocalRollbackOnly()) {  
  8. if  (defStatus.isDebug()) {  
  9. logger.debug("Transactional code has requested rollback" );  
  10. }  
  11. processRollback(defStatus);  
  12. return ;  
  13. }  
  14. if  (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {  
  15. if  (defStatus.isDebug()) {  
  16. logger.debug("Global transaction is marked as rollback-only but transactional code requested commit" );  
  17. }  
  18. processRollback(defStatus);  
  19. throw   new  UnexpectedRollbackException(  
  20. "Transaction has been rolled back because it has been marked as rollback-only" );  
  21. }  
  22. processCommit(defStatus);  
  23. }  
  1. public   final   void  commit(TransactionStatus status)  throws  TransactionException {  
  2. DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;  
  3. if  (defStatus.isCompleted()) {  
  4. throw   new  IllegalTransactionStateException(  
  5. "Transaction is already completed - do not call commit or rollback more than once per transaction" );  
  6. }  
  7. if  (defStatus.isLocalRollbackOnly()) {  
  8. if  (defStatus.isDebug()) {  
  9. logger.debug("Transactional code has requested rollback" );  
  10. }  
  11. processRollback(defStatus);  
  12. return ;  
  13. }  
  14. if  (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {  
  15. if  (defStatus.isDebug()) {  
  16. logger.debug("Global transaction is marked as rollback-only but transactional code requested commit" );  
  17. }  
  18. processRollback(defStatus);  
  19. throw   new  UnexpectedRollbackException(  
  20. "Transaction has been rolled back because it has been marked as rollback-only" );  
  21. }  
  22. processCommit(defStatus);  
  23. }  


上面這段代碼就是transactionmanager中的commit,但是看上去,它又把自己的職責分配給別人了,從代碼裏我們看到, 如果事務已經結束了就拋異常,如果事務是rollbackonly的,那麼就rollback吧,但是按照正常流程,我們還是想來看一下,事務的提交,就 是processCommit(status)這個方法吧。

代碼
  1. private   void  processCommit(DefaultTransactionStatus status)  throws  TransactionException {  
  2. try  {  
  3. boolean  beforeCompletionInvoked =  false ;  
  4. try  {  
  5. triggerBeforeCommit(status);  
  6. triggerBeforeCompletion(status);  
  7. beforeCompletionInvoked = true ;  
  8. if  (status.hasSavepoint()) {  
  9. if  (status.isDebug()) {  
  10. logger.debug("Releasing transaction savepoint" );  
  11. }  
  12. status.releaseHeldSavepoint();  
  13. }  
  14. else   if  (status.isNewTransaction()) { //這個判斷非常重要,下面會詳細講解這個判斷的作用   
  15. if  (status.isDebug()) {  
  16. logger.debug("Initiating transaction commit" );  
  17. }  
  18. boolean  globalRollbackOnly = status.isGlobalRollbackOnly();  
  19. doCommit(status);  
  20. // Throw UnexpectedRollbackException if we have a global rollback-only   
  21. // marker but still didn't get a corresponding exception from commit.   
  22. `````````````````````  
  23. }  
  1. private   void  processCommit(DefaultTransactionStatus status)  throws  TransactionException {  
  2. try  {  
  3. boolean  beforeCompletionInvoked =  false ;  
  4. try  {  
  5. triggerBeforeCommit(status);  
  6. triggerBeforeCompletion(status);  
  7. beforeCompletionInvoked = true ;  
  8. if  (status.hasSavepoint()) {  
  9. if  (status.isDebug()) {  
  10. logger.debug("Releasing transaction savepoint" );  
  11. }  
  12. status.releaseHeldSavepoint();  
  13. }  
  14. else   if  (status.isNewTransaction()) { //這個判斷非常重要,下面會詳細講解這個判斷的作用   
  15. if  (status.isDebug()) {  
  16. logger.debug("Initiating transaction commit" );  
  17. }  
  18. boolean  globalRollbackOnly = status.isGlobalRollbackOnly();  
  19. doCommit(status);  
  20. // Throw UnexpectedRollbackException if we have a global rollback-only   
  21. // marker but still didn't get a corresponding exception from commit.   
  22. `````````````````````  
  23. }  


我們注意到,在判斷一個事務是否是新事務之前還有一個status.hasSavepoint()的判斷,我認爲這個判斷事實上就是嵌套事 務的判斷,即判斷這個事務是否是嵌套事務,如果不是嵌套事務,則再判斷它是否是一個新事務,下面這段話就非常重要了,BService的中的方法是先出棧 的,也就是說在調用BService之前的創建的那個事務狀態對象在這裏要先被判斷,但是由於在調用BService的方法之前已經創建了一個 Transaction 和Session(假設我們使用的是hibernate3),這時候在創建第二個TransactionInfo(再強調一下吧,TransactionInfo並不是TransactionTransaction 是 真正的事務對象,TransactionInfo只不過是一個輔助類而已,用來記錄一系列狀態的輔助類)的TransactionStatus的時候就會 進入下面這個方法(當然在這之前會判斷一下當前線程中是否已經有了一個SessionHolder對象,不清楚SessionHolder作用的同學情況 第一篇文章),這個方法其實應該放到第一篇文章中講的,但是想到如果不講事務提交就講這個方法好像沒有這麼貼切,廢話少說,我們來看一下吧:

代碼
  1. private  TransactionStatus handleExistingTransaction(  
  2. TransactionDefinition definition, Object transaction, boolean  debugEnabled)  
  3. throws  TransactionException {  
  4. if  (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {  
  5. throw   new  IllegalTransactionStateException(  
  6. "Transaction propagation 'never' but existing transaction found" );  
  7. }  
  8. if  (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {  
  9. if  (debugEnabled) {  
  10. logger.debug("Suspending current transaction" );  
  11. }  
  12. Object suspendedResources = suspend(transaction);  
  13. boolean  newSynchronization = ( this .transactionSynchronization == SYNCHRONIZATION_ALWAYS);  
  14. return  newTransactionStatus(  
  15. definition, null false , newSynchronization, debugEnabled, suspendedResources);  
  16. }  
  17. if  (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {  
  18. if  (debugEnabled) {  
  19. logger.debug("Suspending current transaction, creating new transaction with name ["  +  
  20. definition.getName() + "]" );  
  21. }  
  22. Object suspendedResources = suspend(transaction);  
  23. doBegin(transaction, definition);  
  24. boolean  newSynchronization = ( this .transactionSynchronization != SYNCHRONIZATION_NEVER);  
  25. return  newTransactionStatus(  
  26. definition, transaction, true , newSynchronization, debugEnabled, suspendedResources);  
  27. }  
  28. if  (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {  
  29. if  (!isNestedTransactionAllowed()) {  
  30. throw   new  NestedTransactionNotSupportedException(  
  31. "Transaction manager does not allow nested transactions by default - "  +  
  32. "specify 'nestedTransactionAllowed' property with value 'true'" );  
  33. }  
  34. if  (debugEnabled) {  
  35. logger.debug("Creating nested transaction with name ["  + definition.getName() +  "]" );  
  36. }  
  37. if  (useSavepointForNestedTransaction()) {  
  38. // Create savepoint within existing Spring-managed transaction,   
  39. // through the SavepointManager API implemented by TransactionStatus.   
  40. // Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.   
  41. DefaultTransactionStatus status =  
  42. newTransactionStatus(definition, transaction, false false , debugEnabled,  null );  
  43. status.createAndHoldSavepoint();  
  44. return  status;  
  45. }  
  46. else  {  
  47. // Nested transaction through nested begin and commit/rollback calls.   
  48. // Usually only for JTA: Spring synchronization might get activated here   
  49. // in case of a pre-existing JTA transaction.   
  50. doBegin(transaction, definition);  
  51. boolean  newSynchronization = ( this .transactionSynchronization != SYNCHRONIZATION_NEVER);  
  52. return  newTransactionStatus(definition, transaction,  true , newSynchronization, debugEnabled,  null );  
  53. }  
  54. }  
  55. // Assumably PROPAGATION_SUPPORTS.   
  56. if  (debugEnabled) {  
  57. logger.debug("Participating in existing transaction" );  
  58. }  
  59. boolean  newSynchronization = ( this .transactionSynchronization != SYNCHRONIZATION_NEVER);  
  60. return  newTransactionStatus(definition, transaction,  false , newSynchronization, debugEnabled,  null );  
  61. }  
  1. private  TransactionStatus handleExistingTransaction(  
  2. TransactionDefinition definition, Object transaction, boolean  debugEnabled)  
  3. throws  TransactionException {  
  4. if  (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {  
  5. throw   new  IllegalTransactionStateException(  
  6. "Transaction propagation 'never' but existing transaction found" );  
  7. }  
  8. if  (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {  
  9. if  (debugEnabled) {  
  10. logger.debug("Suspending current transaction" );  
  11. }  
  12. Object suspendedResources = suspend(transaction);  
  13. boolean  newSynchronization = ( this .transactionSynchronization == SYNCHRONIZATION_ALWAYS);  
  14. return  newTransactionStatus(  
  15. definition, null false , newSynchronization, debugEnabled, suspendedResources);  
  16. }  
  17. if  (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {  
  18. if  (debugEnabled) {  
  19. logger.debug("Suspending current transaction, creating new transaction with name ["  +  
  20. definition.getName() + "]" );  
  21. }  
  22. Object suspendedResources = suspend(transaction);  
  23. doBegin(transaction, definition);  
  24. boolean  newSynchronization = ( this .transactionSynchronization != SYNCHRONIZATION_NEVER);  
  25. return  newTransactionStatus(  
  26. definition, transaction, true , newSynchronization, debugEnabled, suspendedResources);  
  27. }  
  28. if  (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {  
  29. if  (!isNestedTransactionAllowed()) {  
  30. throw   new  NestedTransactionNotSupportedException(  
  31. "Transaction manager does not allow nested transactions by default - "  +  
  32. "specify 'nestedTransactionAllowed' property with value 'true'" );  
  33. }  
  34. if  (debugEnabled) {  
  35. logger.debug("Creating nested transaction with name ["  + definition.getName() +  "]" );  
  36. }  
  37. if  (useSavepointForNestedTransaction()) {  
  38. // Create savepoint within existing Spring-managed transaction,   
  39. // through the SavepointManager API implemented by TransactionStatus.   
  40. // Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.   
  41. DefaultTransactionStatus status =  
  42. newTransactionStatus(definition, transaction, false false , debugEnabled,  null );  
  43. status.createAndHoldSavepoint();  
  44. return  status;  
  45. }  
  46. else  {  
  47. // Nested transaction through nested begin and commit/rollback calls.   
  48. // Usually only for JTA: Spring synchronization might get activated here   
  49. // in case of a pre-existing JTA transaction.   
  50. doBegin(transaction, definition);  
  51. boolean  newSynchronization = ( this .transactionSynchronization != SYNCHRONIZATION_NEVER);  
  52. return  newTransactionStatus(definition, transaction,  true , newSynchronization, debugEnabled,  null );  
  53. }  
  54. }  
  55. // Assumably PROPAGATION_SUPPORTS.   
  56. if  (debugEnabled) {  
  57. logger.debug("Participating in existing transaction" );  
  58. }  
  59. boolean  newSynchronization = ( this .transactionSynchronization != SYNCHRONIZATION_NEVER);  
  60. return  newTransactionStatus(definition, transaction,  false , newSynchronization, debugEnabled,  null );  
  61. }  


我們看到這個方法其實很明瞭,就是什麼樣的傳播途徑就創建什麼樣的transactionstatus,這個方法是在事務開始時被調用的, 拿到我們之前舉的例子中來看下,我們就恍然大悟了,原來,如果之前已經創建過事務,那個這個新建的transactionstauts就不應該是屬於一個 newTransaction了,所以第3個參數就是false了。

也就是說,在BService的方法出棧要要執行processcommit,但是由於BService的那個TransactionStatus不是一個newTransaction,所以它根本不會觸發這個動作:

代碼
  1. else   if  (status.isNewTransaction()) { //這個判斷非常重要,下面會詳細講解這個判斷的作用   
  2. if  (status.isDebug()) {  
  3. logger.debug("Initiating transaction commit" );  
  4. }  
  5. boolean  globalRollbackOnly = status.isGlobalRollbackOnly();  
  6. doCommit(status);  
  7. }  
  1. else   if  (status.isNewTransaction()) { //這個判斷非常重要,下面會詳細講解這個判斷的作用   
  2. if  (status.isDebug()) {  
  3. logger.debug("Initiating transaction commit" );  
  4. }  
  5. boolean  globalRollbackOnly = status.isGlobalRollbackOnly();  
  6. doCommit(status);  
  7. }  


也就是說在BService的方法出棧後,事務是不會提交的。這完全符合propragation_required 的模型。
而 在AService的方法出棧後,AService的方法所對應的那個TransactionStatus對象的newTransaction屬性是爲 true的,即它會觸發上面這段代碼,進行真正的事務提交。讓我們回想一下AService方法入棧之前創建TransactionStatus對象的情 形吧:
newTransactionStatus(definition, transaction , true, newSynchronization, debugEnabled, null);看到第3個參數爲true沒有。

那麼事務該提交了吧,事務的提交我想使用過hibernate的人都知道怎麼提交了:
txObject.getSessionHolder().getTransaction().commit();
從當前線程中拿到SessionHolder,再拿到開始事務的那個Transaction 對象,然後再commit事務。在沒有用spring 之前,我們經常這麼做。呵呵。

好吧,我已經說到了spring 聲 明式事務管理的70%到80%的內容了,這70%到80%的內容看上去還是非常容易理解的,如果把這兩篇文章認真看過,我相信會有所收穫的,剩下的內容需 要靠大家自己去挖掘了,因爲另剩下的內容可是需要花費很多時間的,因爲牽扯的東西實在是太多了,呵呵。最後祝大家閱讀愉快,因爲我的文筆實在是讓大家的眼 睛受罪了。 

http://www.javaeye.com/topic/89072

=================================================

用 Spring 框架指定自定義隔離級別

http://www.ibm.com/developerworks/cn/java/j-isolation/

在 Java EE 應用程序的分佈式事務中使用自定義隔離級別

developerWorks
文檔選項
將此頁作爲電子郵件發送

將此頁作爲電子郵件發送



級別: 中級

Ricardo Olivieri ([email protected] ), 軟件工程師, IBM

2006 年 11 月 20 日

如果您正在構建一個應用程序,該應用程序要求在執行用例時在全局事務中具有自定義隔離級別,您可能已經發現這是一件困難的事,因 爲 Java™ Transaction API 並不提供對自定義隔離級別的支持。幸運地是,Spring 框架允許您設計在全局事務中使用自定義隔離級別的 Web 和企業應用程序,但這卻不是一件容易的事。在本文中,Ricardo Olivieri 用 7 個詳細的步驟演示了這一過程。

許多 Java Enterprise Edition(EE)應用程序在執行用戶請求時都會訪問多處資源。例如,應用程序也許需要將一條消息放到一個面向消息的中間件隊列中,並在相同的事務上 下文中更新數據庫行。可以通過使用應用服務器提供的 Java Transaction API(JTA)事務管理器和兼容 XA 的驅動程序連接到數據資源來實現這一任務。但應用程序的需求也許會在執行一個用例時調用全局事務中的自定義隔離級別(custom isolation level) —— JTA 事務管理器並不支持自定義隔離級別。如果正在使用 Spring 框架,出這個原因,如果爲 Spring 配置文件中的全局事務指定一個自定義隔離級別,將會拋出一個異常。

本文展示了一種能夠 使用 Spring 來指定全局事務中的自定義隔離級別的方法。如果您部署應用程序的應用服務器,允許在定義數據源的位置指定作爲數據庫訪問的隔離級別值,那麼該方法都是有效 的。爲從本文中獲益,您應該熟悉 Spring 框架並理解如何在 Spring 配置文件中定義事務代理及面向方面的 advice。在對應用服務器熟悉的前提下,也假設您熟悉 Java EE 設計模式和全局/分佈式事務的概念。

問題

軟件應用程序的需求也許做了這樣的規定(這裏的許多技術超出了本文討論範圍),即在執行一個給定用例的過程中,必須將相同的隔離級別使用到所有的數 據訪問中。需求也許還這樣規定,在一個用例實現中只要訪問了兩項或超過兩項的外部資源,該應用程序就應該使用全局事務。例如,作爲用例實現的一部分,應用 程序也許會查詢兩個不同的數據庫表並將一條消息放到消息隊列中。針對這個用例的設計也許需要使用 “已提交讀” 隔離級別來執行兩個數據庫 READ 操作。但也需要在執行不同的 用例時,應用程序會使用不同的隔離級別(如 “可重複讀”)來執行這兩個相同數據庫的 READ 操作。在這兩個用例的執行中,應用程序執行相同的數據庫操作和部分相同的代碼段,但卻必須使用不同的隔離級別。

您可以分別爲兩個 READ 操作定義方法,並以要使用的隔離級別作爲參數。這些方法的調用者會依據執行中的用例來指定相應的隔離級別。但即使這種方法會起作用,將這種邏輯包含在 Java 代碼中並不是最佳方法,且維護代碼會很困難。表面上看,利用 Spring 框架的功能似乎是更好的方法。Spring 是一個強大的框架,這在很大程度上是由於其爲應用程序定義事務的強大功能。Spring 讓您用一種清晰的方式指定事務屬性,如隔離級別、傳播行爲和異常處理行爲(例如,當拋出特定的異常時,事務是否應該自動回滾)。但缺乏對指定自定義隔離級 別的支持是 JTA 是一塊軟肋,如下列場景所說明的那樣。

什麼是服務對象?
在本文的上下文中,可以把服務對象 想象成負責隱藏業務組件並集中工作流的門面(facade)。它們的方法爲應用程序的使用場景定義了實現。服務對象爲客戶機(Web UI、遠程服務客戶機,等等)提供粗糙的界面。Jave EE Session Facade 設計模式(參見 參考資料 )很好地適應了服務對象。(在 EJB 世界中,會話的門面是由企業會話 bean 來實現的。)

Spring 場景

使用 JTA 事務管理器的新手或只對它瞭解一點的開發人員也許想要爲服務對象(如 OrderService )(參見 什麼是服務對象? )的實現定義(在 Spring 配置文件中)一個事務代理,如清單 1 所示:


清單 1. 使用 JTA 事務管理器的事務代理的錯誤定義
				



<bean id="transactionManager"

class="org.springframework.transaction.jta.JtaTransactionManager">

<constructor-arg>

<ref local="jtaTransactionManager" />

</constructor-arg>

</bean>



<bean id="orderService"

class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">

<property name="transactionManager">

<ref local="transactionManager" />

</property>

<property name="proxyInterfaces">

<list>

<value>sample.services.OrderService</value>

</list>

</property>

<property name="target">

<ref local="orderServiceTarget" />

</property>

<property name="transactionAttributes">

<props>

<prop key="save*">PROPAGATION_REQUIRED, ISOLATION_SERIALIZABLE</prop>

<prop key="delete*">PROPAGATION_REQUIRED, ISOLATION_READ_UNCOMMITTED</prop>

<prop key="find*">PROPAGATION_REQUIRED, ISOLATION_READ_UNCOMMITTED,

readOnly</prop>

</props>

</property>

</bean>


清單 1 中定義了兩個 bean。第一個 bean 的定義指定了應用程序將使用的事務管理器。正如您能看到的那樣,這個 bean 依賴於另一個叫做 jtaTransactionManager 的 bean,而這個 bean 的定義依賴於您正在使用的應用服務器。例如,對於 IBM WebSphere Application Server 來說,這個 bean 的定義是這樣的:

 

<bean id="jtaTransactionManager"

class="org.springframework.transaction.jta.WebSphereTransactionManagerFactoryBean"

singleton="true" />


爲什麼需要一個 JTA 事務管理器?
可能需要 JTA 事務管理器是因爲當應用程序執行一個用例實現時要訪問多處資源。例如,在數據庫中保存一條記錄時,代碼也會將一條輸出消息放到消息隊列中。要在這種情況下保證數據的集成性和原子性,需要一個支持分佈式事務的事務管理器。

清單 1 中第二個 bean(稱爲 orderService )包含一個服務對象的事務代理定義,該服務對象實現了一個名爲 OrderService 的接口。這個代理爲三個方法聲明瞭三個事務性定義:save()delete()find() 。由於 “序列化” 和 “未提交讀” 被指定爲這些方法的隔離級別,那麼期望這些就是在運行時獲得的隔離級別是符合邏輯的。然而,請注意該代理定義包含了對 JTA 事務管理器的引用。如果用這個配置運行應用程序,您也許會十分驚詫。只要執行了 OrderService 實現的 save()delete()find() 方法,就會出現這樣一個異常:

 

org.springframework.transaction.InvalidIsolationLevelException:

JtaTransactionManager does not support custom isolation levels

at org.springframework.transaction.jta.JtaTransactionManager.applyIsolationLevel(

JtaTransactionManager.java:617)

at org.springframework.transaction.jta.JtaTransactionManager.doJtaBegin(

JtaTransactionManager.java:595)

at org.springframework.transaction.jta.JtaTransactionManager.doBegin(

JtaTransactionManager.java:559)

at org.springframework.transaction.support.AbstractPlatformTransactionManager.

getTransaction(AbstractPlatformTransactionManager.java:234)

...


出現這個錯誤是因爲 JTA 事務管理器不支持自定義隔離級別。當使用 JTA 事務管理器時,事務代理的 bean 定義會和清單 2 中的類似:


清單 2. 使用 JTA 事務管理器的事務代理的正確定義
				

<bean id="orderService"

class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">

<property name="transactionManager">

<ref local="transactionManager" />

</property>

<property name="proxyInterfaces">

<list>

<value>sample.services.OrderService</value>

</list>

</property>

<property name="target">

<ref local="orderServiceTarget" />

</property>

<property name="transactionAttributes">

<props>

<prop key="save*">PROPAGATION_REQUIRED, ISOLATION_DEFAULT</prop>

<prop key="delete*">PROPAGATION_REQUIRED, ISOLATION_DEFAULT</prop>

<prop key="find*">PROPAGATION_REQUIRED, ISOLATION_DEFAULT,readOnly

</prop>

</props>

</property>

</bean>


請注意,和 清單 1 惟一的區別是,現在所有的隔離級別都被設置爲 ISOLATION_DEFAULT 。如果要用 清單 2 中的事務配置執行一個應用程序,該代碼會順利運行。然而,您很可能想知道當執行 save()delete()find() 方法時,使用哪個隔離級別。這個問題的答案取決於 “其依賴項”。隔離級別依賴於用於與數據庫通信的數據源。

圖 1 中的序列圖說明了在執行 save() 方法時,OrderService 實現對象和兩個數據訪問對象(DAO)的交互。(正如從您的經驗中得出的那樣,DAO 主要用於將業務邏輯從存儲訪問/持久性代碼中分離出來。)


圖 1. OrderService 實現的 save() 方法的序列圖
Save 命令用例序列圖

這裏 看全圖。

在執行 OrderService 實現的 save() 方法時使用的隔離級別由在 OrderDAOCustomerDAO 數據訪問對象中引用的數據源所聲明。例如,如果 OrderDAO 被配置爲從定義爲具有 “未提交讀” 隔離級別的數據源中獲取連接,而 CustomerDAO 被配置爲使用定義爲具有 “序列化” 隔離級別的數據源,然後在通過 OrderDAO 對象訪問數據時, save() 方法會使用 “未提交讀” 隔離級別,而在通過 CustomerDAO 訪問數據時,使用 “序列化” 隔離級別。但如果再回過頭來看 清單 1 ,就會發現這並不是預期的目的。相反,在一個用例執行中,單個的隔離級別將被用於所有的數據訪問(如 save()delete()find() 方法),即使不同的用例執行相同的數據庫操作,並且對數據訪問對象執行相同的調用集。繼續讀下去,看看如何實現這一目標。





回頁首


解決方案

步驟 1

該解決方案是一個由 7 個步驟組成的過程,在此過程中利用了名爲 JdbcOperations 的 Spring 接口,該接口可以在 org.springframework.jdbc.core 包中找到。正如 Spring 文檔中所描述的那樣,該接口能被輕易地模擬或保存。第一步是要創建一個名爲 JdbcOperationsImpl 的類,該類實現 JdbcOperations 接口。該類也實現 ApplicationContextAware 接口。

JdbcOperations 接口需要許多數據庫訪問操作的實現。當然,您不應該(也不應該想要)編寫如此低層的代碼。相反,此類的目的僅僅是作爲一個代理,該代理將所有的數據訪問調用轉發至一個 org.springframework.jdbc.core.JdbcTemplate 實例。

您也許會回想起之前用 Spring 編寫數據訪問代碼的經歷,可以輕易地通過將一個 javax.sql.DataSource 實例傳給 JdbcTemplate 的構造函數將其實例化。請記住,本文假設您正在使用一個應用服務器,該服務器將數據源定義作爲隔離級別值的佔位符。爲在執行用例時使用相同的隔離級別,必須在執行該用例時,使用相同的 JdbcTemplate 實例來跨越所有的數據訪問對象。換言之,依賴於執行中的用例,數據訪問對象需要獲得對 JdbcTemplate 實例的引用,該實例與(通過其 DataSource 對象)相應的隔離級別值相關聯。

ApplicationContextAware 接口需要 setApplicationContext() 方法的一個實現,該方法將實現類的訪問提供給 Spring 應用程序的上下文。正如稍後將會看到的那樣,訪問 Spring 的上下文是必需的,因爲 JdbcOperationsImpl 使用它來獲取 bean(通過其 ID)。JdbcOperationsImpl 類的 bean 定義如清單 3 所示:


清單 3. JdbcOperationsImpl 實例的定義
				

<bean id="jdbcOperations"

class="application.storage.JdbcOperationsImpl" singleton="true">

<constructor-arg index="0">

<!-- Reference to a JdbcTemplate instance with a

"read committed" isolation level -->

<ref local="rcJdbcTemplate" />

</constructor-arg>

</bean>


步驟 2

第二步是要確保所有的數據訪問對象使用 JdbcOperationsImpl 類的一個實例來與數據庫進行通信,而不是 JdbcTemplate 實例。這是很明顯的,因爲 JdbcTemplate 類實現 JdbcOperations 接口。不需要改變數據訪問對象中一行代碼;只需要改變 Spring 配置文件中每個數據訪問對象的配置。例如,最初的 OrderDAO 數據訪問對象的定義是這樣的:

<bean id="orderDAO"

class="sample.dao.OrderDAOImpl" singleton="true">

<property name="jdbcOperations">

<ref local="jdbcTemplate" />

</property>

</bean>


請將 OrderDAO 數據訪問對象的定義改成這樣:

<bean id="orderDAO"

class="sample.dao.OrderDAOImpl" singleton="true">

<property name="jdbcOperations">

<ref local="jdbcOperations" />

</property>

</bean>


現在,JdbcOperationsImpl 類中的所有訪問存儲資源(如 batchUpdate()execute() 方法)的方法都調用一個名爲 getJdbcTemplate() 的方法,如清單 4 所示:


清單 4. JdbcOperationsImpl 類中 getJdbcTemplate() 方法的實現
				

private JdbcTemplate getJdbcTemplate() {

try {

return (JdbcTemplate) applicationContext.getBean("jdbcTemplate");

} catch (ClassCastException e) {

logger.warn(

"Using default JdbcTemplate instance.", e);



return defaultJdbcTemplate;

}

}


在這段代碼中,getJdbcTemplate() 方法查詢 Spring 應用程序的上下文以獲取相應的 JdbcTemplate 實例。請注意,使用了 jdbcTemplatebean id 來查詢上下文。同樣,請注意如果在 getJdbcTemplate() 獲取 JdbcTemplate 對象時發生錯誤,將返回對默認 JdbcTemplate 對象的引用。defaultJdbcTemplate 對象是使用 “已提交讀” 隔離級別的 JdbcOperationsImpl 類的 JdbcTemplate 實例變量。JdbcOperationsImpl 類使用這個實例變量作爲後備解決方案,以防相應的 JdbcTemplate 實例不能從應用程序的上下文中獲取。(當發生這種情況時,會在日記中記一個警告。)此類的構造函數期望將默認的 JdbcTemplate 實例作爲一個參數,如清單 5 所示:


清單 5. JdbcOperationsImpl 類的構造函數
				

public JdbcOperationsImpl(JdbcTemplate defaultJdbcTemplate) {

super();

this.defaultJdbcTemplate = defaultJdbcTemplate;

}


從清單 6 中可見,只要要求應用程序的上下文返回標識爲 jdbcTemplate 的對象,就會調用 IsolationLevelUtil 類的 getJdbcTemplate() 方法:


清單 6. jdbcTemplate bean 的定義
				

<bean id="jdbcTemplate"

class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">

<property name="targetClass">

<value>application.services.IsolationLevelUtil</value>

</property>

<property name="targetMethod">

<value>getJdbcTemplate</value>

</property>

<property name="singleton">

<value>false</value>

</property>

</bean>


步驟 3

第三步是用 清單 6 顯示的定義更新 Spring 配置文件,並定義 IsolationLevelUtil 類的實現,如清單 7 所示:


清單 7. IsolationLevelUtil 類的實現
				

public class IsolationLevelUtil {



private static final ThreadLocal threadJdbcTemplate = new ThreadLocal();



private IsolationLevelUtil() {

super();

}



public static JdbcTemplate getJdbcTemplate() {

JdbcTemplate jdbcTemplate = (JdbcTemplate) threadJdbcTemplate.get();

return jdbcTemplate;

}



public static void setJdbcTemplate(JdbcTemplate jdbcTemplate) {

threadJdbcTemplate.set(jdbcTemplate);

}

}


IsolationLevelUtil 類的 getJdbcTemplate() 方法返回和當前執行線程關聯在一起的 JdbcTemplate 實例。名爲 threadJdbcTemplate 的本地線程變量被用於保持線程和 JdbcTemplate 實例間的關聯。您也許想知道爲什麼 JdbcOperationsImpl 類的 getJdbcTemplate() 方法沒有顯式地調用 IsolationLevelUtilgetJdbcTemplate() 方法。儘管這個方法會起作用,但更好的設計是讓這兩個類保持解耦。例如,如果想要實現一種不同的機制來獲取和執行中的用例相應的 JdbcTemplate 實例,只需要改變 Spring 配置文件,而不是 JdbcOperationsImpl 類。

步驟 4

如果您正在思考哪個組件將相應的 JdbcTemplate 實例設置爲 IsolationLevelUtil 類上的本地線程變量,您的思路是正確的。爲此,這個值必須在線程執行的前期已經設置好了。否則,將返回 NULL 值。所以,第四步是編寫一個負責設置名爲 threadJdbcTemplate 的本地線程變量的組件。請將這個組件實現爲一個名爲 IsolationLevelAdvice 的面向方面的 advice,如清單 8 所示。這個 advice 在用例開始執行前即被應用。


清單 8. IsolationLevelAdvice 類的實現
				

public class IsolationLevelAdvice implements MethodInterceptor {



private Map methodJdbcTemplateMap;



private JdbcTemplate defaultJdbcTemplate;



public IsolationLevelAdvice(Map methodJdbcTemplateMap,

JdbcTemplate defaultJdbcTemplate) {



super();

this.defaultJdbcTemplate = defaultJdbcTemplate;

this.methodJdbcTemplateMap = methodJdbcTemplateMap;

}



public Object invoke(MethodInvocation invocation) throws Exception {

boolean set = false;

try {

Method method = invocation.getMethod();

set = setThreadJdbcTemplate(method);

Object rval = invocation.proceed();

return rval;

} finally {

if (set) {

unsetThreadJdbcTemplate();

}

}

}



public boolean setThreadJdbcTemplate(Method method) {



boolean set = false;

if (IsolationLevelUtil.getJdbcTemplate() == null) {

JdbcTemplate jdbcTemplate = null;

String methodName = method.getName();

Iterator methodPatterns = methodJdbcTemplateMap.keySet().iterator();

while (methodPatterns.hasNext()) {

String methodPattern = (String) methodPatterns.next();

if (Pattern.matches(methodPattern, methodName)) {

jdbcTemplate = (JdbcTemplate)

methodJdbcTemplateMap.get(methodPattern);

break;

}

}

if (jdbcTemplate == null) {

jdbcTemplate = defaultJdbcTemplate;

}



IsolationLevelUtil.setJdbcTemplate(jdbcTemplate);

set = true;

}

return set;

}



public void unsetThreadJdbcTemplate() {

IsolationLevelUtil.setJdbcTemplate(null);

}

}


在該應用程序中,每個服務對象實現都需要此類的實例。

步驟 5

第五步是要在 Spring 配置文件中定義這個類的一個 bean 定義,該 bean 將和 OrderService 實現類關聯起來,如清單 9 所示:


清單 9.針對 OrderService 實現的隔離 advice bean 的定義
				

<bean id="orderServiceIsolationAdvice"

class="application.services.IsolationLevelAdvice" singleton="true">

<constructor-arg index="0">

<map>

<entry key="save.*">

<ref local="rrJdbcTemplate" />

</entry>

<entry key="delete.*">

<ref local="rcJdbcTemplate" />

</entry>

<entry key="find.*">

<ref local="rcJdbcTemplate" />

</entry>

</map>

</constructor-arg>

<constructor-arg index="1">

<ref local="rcJdbcTemplate" />

</constructor-arg>

</bean>


清單 9 中 bean 的定義顯示了 IsolationLevelAdvice 類的實例的構造函數將一個對象映射表作爲第一個參數。這個映射表使用字符串匹配模式作爲定義在 OrderService 接口中方法的名稱的鍵。這些模式中的每一個都被映射到一個 JdbcTemplate 實例中,該實例具有必須用於用例執行的隔離級別。構造函數的第二個參數指定 JdbcTemplate 實例,使用該實例是爲了防止沒有 JdbcTemplate 對象被映射到已經調用的方法中。如果在 清單 8 中仔細觀察這個類的實現,會看到 IsolationLevelAdvice 實例將在運行時使用反射來確定要在 OrderService 實現對象上調用哪個方法。在確定了將執行的方法的名稱後,該 advice 實例查詢 methodJdbcTemplateMap 實例變量(methodJdbcTemplateMap 對象是對這個類的構造函數中第一個參數的引用)來確定在執行該用例時要使用哪個 JdbcTemplate

步驟 6

第六步是要指定 IsolationLevelAdvice bean(被標識爲 orderServiceIsolationAdvice )和 OrderService 實現對象間的關聯。清單 10 中顯示的 bean 定義通過讓 Spring 容器(被 IsolationLevelAdvice 實例標識爲 orderServiceIsolationAdvice )充當 OrderService 類實現的 advice 正好完成這項任務:


清單 10. 針對 OrderService 實現的 AOP 代理 bean 的定義
				

<bean id="orderServiceTarget" class="org.springframework.aop.framework.ProxyFactoryBean">

<property name="proxyInterfaces">

<value>application.services.OrderService</value>

</property>

<property name="interceptorNames">

<value>orderServiceIsolationAdvice</value>

</property>

<property name="target">

<ref bean="orderServiceImpl" />

</property>

</bean>


步驟 7

第七步也是最後的一步是要定義應用程序所需的 JdbcTemplate 實例。清單 11 顯示了每個實例的定義。每個 JdbcTemplate 定義都有一個對不同數據源對象的引用。由於有四個隔離級別,所以需要四個數據源定義和四個 JdbcTemplate 定義。清單 11 也顯示了這些數據源定義:


清單 11. JdbcTemplate 和數據源對象的定義
				

<!-- "Serializable" isolation level - JdbcTemplate -->

<bean id="sJdbcTemplate"

class="org.springframework.jdbc.core.JdbcTemplate" singleton="true">

<property name="dataSource">

<ref local="sDataSource" />

</property>

</bean>



<!-- "Read repeatable" isolation level - JdbcTemplate -->

<bean id="rrJdbcTemplate"

class="org.springframework.jdbc.core.JdbcTemplate" singleton="true">

<property name="dataSource">

<ref local="rrDataSource" />

</property>

</bean>



<!-- "Read committed" isolation level - JdbcTemplate -->

<bean id="rcJdbcTemplate"

class="org.springframework.jdbc.core.JdbcTemplate" singleton="true">

<property name="dataSource">

<ref local="rcDataSource" />

</property>

</bean>



<!-- "Read uncommitted" isolation level - JdbcTemplate -->

<bean id="ruJdbcTemplate"

class="org.springframework.jdbc.core.JdbcTemplate" singleton="true">

<property name="dataSource">

<ref local="ruDataSource" />

</property>

</bean>



<!-- "Serializable" isolation level - data source -->

<bean id="sDataSource"

class="org.springframework.jndi.JndiObjectFactoryBean">

<property name="jndiName">

<value>java:comp/env/jdbc/s_ds</value>

</property>

</bean>



<!-- "Repeatable read" isolation level - data source -->

<bean id="rrDataSource"

class="org.springframework.jndi.JndiObjectFactoryBean">

<property name="jndiName">

<value>java:comp/env/jdbc/rr_ds</value>

</property>

</bean>



<!-- "Read committed" isolation level - data source -->

<bean id="rcDataSource"

class="org.springframework.jndi.JndiObjectFactoryBean">

<property name="jndiName">

<value>java:comp/env/jdbc/rc_ds</value>

</property>

</bean>



<!-- "Read uncommitted" isolation level - data source -->

<bean id="ruDataSource"

class="org.springframework.jndi.JndiObjectFactoryBean">

<property name="jndiName">

<value>java:comp/env/jdbc/ru_ds</value>

</property>

</bean>


圖 2 中的類圖擷取了這些類中存在的關係,定義這些類是爲了實現我所描述過的解決方案:


圖 2. 本文解決方案的類圖
類圖

這裏 查看全圖。

在這個類圖中顯示的大多數關係並沒有定義在 Java 源代碼中,而是在 Spring 配置文件中。(這對 Spring 用戶來說並不奇怪。)同樣,如果將我探討過的 Spring bean 的定義和該類圖中的實體作比較,很容易看出,在 圖 2 中被標識爲 orderServiceIsolationAdvicerrTemplatercTemplate 的類在本質上並不是 Java 類。這三個類中的每個類都有一個 Spring bean 的定義(而不是 Java 類文件)。爲在類圖中傳達這個信息,我使用了在 IsolationLevelAdvice 類和 orderServiceIsolationAdvice 間以及在 JdbcTemplate 類和 rrTemplatercTemplate 間的 “綁定關係”。orderServiceIsolationAdvicerrTemplatercTemplate 實體只不過是通過將其模板類的參數和實際值綁定起來從而實例化其相應的 “模板類” 的具體對象。

下載 這些類的完整的源代碼,您需要這些類來實現我在本文中演示的解決方案。

發佈了73 篇原創文章 · 獲贊 16 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章