在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方法
- public Object invoke(MethodInvocation invocation) throws Throwable {
- // Work out the target class: may be < code > null </ code > .
- // The TransactionAttributeSource should be passed the target class
- // as well as the method, which may be from an interface
- Class targetClass = (invocation.getThis() != null) ? invocation.getThis().getClass() : null;
- // Create transaction if necessary.
- TransactionInfo txInfo = createTransactionIfNecessary (invocation.getMethod(), targetClass);
- Object retVal = null ;
- try {
- // This is an around advice.
- // Invoke the next interceptor in the chain.
- // This will normally result in a target object being invoked.
- retVal = invocation .proceed();
- }
- catch (Throwable ex) {
- // target invocation exception
- doCloseTransactionAfterThrowing(txInfo, ex);
- throw ex;
- }
- finally {
- doFinally(txInfo);//業務方法出棧後必須先執行的一個方法
- }
- doCommitTransactionAfterReturning(txInfo);
- return retVal;
- }
- public Object invoke(MethodInvocation invocation) throws Throwable {
- // Work out the target class: may be < code > null </ code > .
- // The TransactionAttributeSource should be passed the target class
- // as well as the method, which may be from an interface
- Class targetClass = (invocation.getThis() != null) ? invocation.getThis().getClass() : null;
- // Create transaction if necessary.
- TransactionInfo txInfo = createTransactionIfNecessary (invocation.getMethod(), targetClass);
- Object retVal = null ;
- try {
- // This is an around advice.
- // Invoke the next interceptor in the chain.
- // This will normally result in a target object being invoked.
- retVal = invocation .proceed();
- }
- catch (Throwable ex) {
- // target invocation exception
- doCloseTransactionAfterThrowing(txInfo, ex);
- throw ex;
- }
- finally {
- doFinally(txInfo);//業務方法出棧後必須先執行的一個方法
- }
- doCommitTransactionAfterReturning(txInfo);
- return retVal;
- }
其中的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方法裏有這樣一個方法吧:
- protected TransactionInfo createTransactionIfNecessary(Method method, Class targetClass) {
- // We always bind the TransactionInfo to the thread, even if we didn't create
- // a new transaction here. This guarantees that the TransactionInfo stack
- // will be managed correctly even if no transaction was created by this aspect.
- txInfo.bindToThread();
- return txInfo;
- }
- 就是這個bindToThread()方法在作怪:
- private void bindToThread() {
- // Expose current TransactionStatus, preserving any existing transactionStatus for
- // restoration after this transaction is complete.
- oldTransactionInfo = (TransactionInfo) currentTransactionInfo.get();
- currentTransactionInfo.set(this );
- }
- protected TransactionInfo createTransactionIfNecessary(Method method, Class targetClass) {
- // We always bind the TransactionInfo to the thread, even if we didn't create
- // a new transaction here. This guarantees that the TransactionInfo stack
- // will be managed correctly even if no transaction was created by this aspect.
- txInfo.bindToThread();
- return txInfo;
- }
- 就是這個bindToThread()方法在作怪:
- private void bindToThread() {
- // Expose current TransactionStatus, preserving any existing transactionStatus for
- // restoration after this transaction is complete.
- oldTransactionInfo = (TransactionInfo) currentTransactionInfo.get();
- currentTransactionInfo.set(this );
- }
如果當前線程中已經有了一個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 方法吧:
- protected TransactionInfo createTransactionIfNecessary(Method method, Class targetClass) {
- txInfo.newTransactionStatus(this .transactionManager.getTransaction(txAttr));
- }
- protected TransactionInfo createTransactionIfNecessary(Method method, Class targetClass) {
- txInfo.newTransactionStatus(this .transactionManager.getTransaction(txAttr));
- }
再看看transactionManager.getTransaction(txAttr)方法吧:
- public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
- else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
- definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
- definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
- if (debugEnabled) {
- logger.debug("Creating new transaction with name [" + definition.getName() + "]" );
- }
- doBegin(transaction, definition);
- boolean newSynchronization = ( this .transactionSynchronization != SYNCHRONIZATION_NEVER);
- return newTransactionStatus(definition, transaction, true , newSynchronization, debugEnabled, null ); //注意這裏的返回值,返回的就是一個TransactionStatus對象,這個對象表明了一個事務的狀態,比如說是否是一個新的事務,事務是否已經結束,等等,這個對象是非常重要的,在事務提交的時候還是會用到它的。 }
- }
- }
- public
- final TransactionStatus getTransaction(TransactionDefinition
- definition) throws TransactionException {
- else if (definition.getPropagationBehavior() ==
- TransactionDefinition.PROPAGATION_REQUIRED ||
- definition.getPropagationBehavior() ==
- TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
- definition.getPropagationBehavior() ==
- TransactionDefinition.PROPAGATION_NESTED) {
- if (debugEnabled) {
- logger.debug("Creating new transaction with name [" +
- definition.getName() + "]" );
- }
- doBegin(transaction, definition);
- boolean newSynchronization = ( this .transactionSynchronization !=
- SYNCHRONIZATION_NEVER);
- return newTransactionStatus(definition, transaction, true ,
- newSynchronization, debugEnabled,
- null ); //注意這裏的返回值,返回的就是一個TransactionStatus對象,這個對象表明了一個事務的狀態,比如說是否是一個新的事務,
- 事務是否已經結束,等等,這個對象是非常重要的,在事務提交的時候還是會用到它的。 }
- }
- }
還有一點需要說明的是,AService的方法在執行之前創建的transactionstatus確實是通過這個方法創建的,但是,BService的方法在執行之前創建transactionstatus的方法就與這個不一樣了,下面會有詳解。
回顧了事務開始時所調用的方法之後,是不是覺得現在對spring 如何處理事務越來越清晰了呢。由於這麼幾個方法的調用,每個方法入棧之前它的事務狀態就已經被設置好了。這個事務狀態就是爲了在方法出棧時被調用而準備的。
讓我們再次回到BService中的方法出棧的那個時間段,看看spring 都 做了些什麼,我們知道,後入棧的肯定是先出棧,BService中的方法後入棧,拿它肯定要先出棧了,它出棧的時候是要判斷是否要提交事務,釋放資源的, 讓我們來看看TransactionInterceptor的invoke的最後那個方法 doCommitTransactionAfterReturning:
- protected void doCommitTransactionAfterReturning(TransactionInfo txInfo) {
- if (txInfo != null && txInfo.hasTransaction()) {
- if (logger.isDebugEnabled()) {
- logger.debug("Invoking commit for transaction on " + txInfo.joinpointIdentification());
- }
- this .transactionManager.commit(txInfo.getTransactionStatus());
- //瞧:提交事務時用到了表明事務狀態的那個TransactionStatus對象了。
- }
- }
- protected void doCommitTransactionAfterReturning(TransactionInfo txInfo) {
- if (txInfo != null && txInfo.hasTransaction()) {
- if (logger.isDebugEnabled()) {
- logger.debug("Invoking commit for transaction on " + txInfo.joinpointIdentification());
- }
- this .transactionManager.commit(txInfo.getTransactionStatus());
- //瞧:提交事務時用到了表明事務狀態的那個TransactionStatus對象了。
- }
- }
看這個方法的名字就知道spring
是要在業務方法出棧時提交事務,貌似很簡單,但是事實是這樣的嗎? 我們接着往下看。
- public final void commit(TransactionStatus status) throws TransactionException {
- DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
- if (defStatus.isCompleted()) {
- throw new IllegalTransactionStateException(
- "Transaction is already completed - do not call commit or rollback more than once per transaction" );
- }
- if (defStatus.isLocalRollbackOnly()) {
- if (defStatus.isDebug()) {
- logger.debug("Transactional code has requested rollback" );
- }
- processRollback(defStatus);
- return ;
- }
- if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
- if (defStatus.isDebug()) {
- logger.debug("Global transaction is marked as rollback-only but transactional code requested commit" );
- }
- processRollback(defStatus);
- throw new UnexpectedRollbackException(
- "Transaction has been rolled back because it has been marked as rollback-only" );
- }
- processCommit(defStatus);
- }
- public final void commit(TransactionStatus status) throws TransactionException {
- DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
- if (defStatus.isCompleted()) {
- throw new IllegalTransactionStateException(
- "Transaction is already completed - do not call commit or rollback more than once per transaction" );
- }
- if (defStatus.isLocalRollbackOnly()) {
- if (defStatus.isDebug()) {
- logger.debug("Transactional code has requested rollback" );
- }
- processRollback(defStatus);
- return ;
- }
- if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
- if (defStatus.isDebug()) {
- logger.debug("Global transaction is marked as rollback-only but transactional code requested commit" );
- }
- processRollback(defStatus);
- throw new UnexpectedRollbackException(
- "Transaction has been rolled back because it has been marked as rollback-only" );
- }
- processCommit(defStatus);
- }
上面這段代碼就是transactionmanager中的commit,但是看上去,它又把自己的職責分配給別人了,從代碼裏我們看到,
如果事務已經結束了就拋異常,如果事務是rollbackonly的,那麼就rollback吧,但是按照正常流程,我們還是想來看一下,事務的提交,就
是processCommit(status)這個方法吧。
- private void processCommit(DefaultTransactionStatus status) throws TransactionException {
- try {
- boolean beforeCompletionInvoked = false ;
- try {
- triggerBeforeCommit(status);
- triggerBeforeCompletion(status);
- beforeCompletionInvoked = true ;
- if (status.hasSavepoint()) {
- if (status.isDebug()) {
- logger.debug("Releasing transaction savepoint" );
- }
- status.releaseHeldSavepoint();
- }
- else if (status.isNewTransaction()) { //這個判斷非常重要,下面會詳細講解這個判斷的作用
- if (status.isDebug()) {
- logger.debug("Initiating transaction commit" );
- }
- boolean globalRollbackOnly = status.isGlobalRollbackOnly();
- doCommit(status);
- // Throw UnexpectedRollbackException if we have a global rollback-only
- // marker but still didn't get a corresponding exception from commit.
- `````````````````````
- }
- private void processCommit(DefaultTransactionStatus status) throws TransactionException {
- try {
- boolean beforeCompletionInvoked = false ;
- try {
- triggerBeforeCommit(status);
- triggerBeforeCompletion(status);
- beforeCompletionInvoked = true ;
- if (status.hasSavepoint()) {
- if (status.isDebug()) {
- logger.debug("Releasing transaction savepoint" );
- }
- status.releaseHeldSavepoint();
- }
- else if (status.isNewTransaction()) { //這個判斷非常重要,下面會詳細講解這個判斷的作用
- if (status.isDebug()) {
- logger.debug("Initiating transaction commit" );
- }
- boolean globalRollbackOnly = status.isGlobalRollbackOnly();
- doCommit(status);
- // Throw UnexpectedRollbackException if we have a global rollback-only
- // marker but still didn't get a corresponding exception from commit.
- `````````````````````
- }
我們注意到,在判斷一個事務是否是新事務之前還有一個status.hasSavepoint()的判斷,我認爲這個判斷事實上就是嵌套事
務的判斷,即判斷這個事務是否是嵌套事務,如果不是嵌套事務,則再判斷它是否是一個新事務,下面這段話就非常重要了,BService的中的方法是先出棧
的,也就是說在調用BService之前的創建的那個事務狀態對象在這裏要先被判斷,但是由於在調用BService的方法之前已經創建了一個
Transaction
和Session(假設我們使用的是hibernate3),這時候在創建第二個TransactionInfo(再強調一下吧,TransactionInfo並不是Transaction
,Transaction
是
真正的事務對象,TransactionInfo只不過是一個輔助類而已,用來記錄一系列狀態的輔助類)的TransactionStatus的時候就會
進入下面這個方法(當然在這之前會判斷一下當前線程中是否已經有了一個SessionHolder對象,不清楚SessionHolder作用的同學情況
第一篇文章),這個方法其實應該放到第一篇文章中講的,但是想到如果不講事務提交就講這個方法好像沒有這麼貼切,廢話少說,我們來看一下吧:
- private TransactionStatus handleExistingTransaction(
- TransactionDefinition definition, Object transaction, boolean debugEnabled)
- throws TransactionException {
- if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
- throw new IllegalTransactionStateException(
- "Transaction propagation 'never' but existing transaction found" );
- }
- if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
- if (debugEnabled) {
- logger.debug("Suspending current transaction" );
- }
- Object suspendedResources = suspend(transaction);
- boolean newSynchronization = ( this .transactionSynchronization == SYNCHRONIZATION_ALWAYS);
- return newTransactionStatus(
- definition, null , false , newSynchronization, debugEnabled, suspendedResources);
- }
- if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
- if (debugEnabled) {
- logger.debug("Suspending current transaction, creating new transaction with name [" +
- definition.getName() + "]" );
- }
- Object suspendedResources = suspend(transaction);
- doBegin(transaction, definition);
- boolean newSynchronization = ( this .transactionSynchronization != SYNCHRONIZATION_NEVER);
- return newTransactionStatus(
- definition, transaction, true , newSynchronization, debugEnabled, suspendedResources);
- }
- if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
- if (!isNestedTransactionAllowed()) {
- throw new NestedTransactionNotSupportedException(
- "Transaction manager does not allow nested transactions by default - " +
- "specify 'nestedTransactionAllowed' property with value 'true'" );
- }
- if (debugEnabled) {
- logger.debug("Creating nested transaction with name [" + definition.getName() + "]" );
- }
- if (useSavepointForNestedTransaction()) {
- // Create savepoint within existing Spring-managed transaction,
- // through the SavepointManager API implemented by TransactionStatus.
- // Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.
- DefaultTransactionStatus status =
- newTransactionStatus(definition, transaction, false , false , debugEnabled, null );
- status.createAndHoldSavepoint();
- return status;
- }
- else {
- // Nested transaction through nested begin and commit/rollback calls.
- // Usually only for JTA: Spring synchronization might get activated here
- // in case of a pre-existing JTA transaction.
- doBegin(transaction, definition);
- boolean newSynchronization = ( this .transactionSynchronization != SYNCHRONIZATION_NEVER);
- return newTransactionStatus(definition, transaction, true , newSynchronization, debugEnabled, null );
- }
- }
- // Assumably PROPAGATION_SUPPORTS.
- if (debugEnabled) {
- logger.debug("Participating in existing transaction" );
- }
- boolean newSynchronization = ( this .transactionSynchronization != SYNCHRONIZATION_NEVER);
- return newTransactionStatus(definition, transaction, false , newSynchronization, debugEnabled, null );
- }
- private TransactionStatus handleExistingTransaction(
- TransactionDefinition definition, Object transaction, boolean debugEnabled)
- throws TransactionException {
- if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
- throw new IllegalTransactionStateException(
- "Transaction propagation 'never' but existing transaction found" );
- }
- if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
- if (debugEnabled) {
- logger.debug("Suspending current transaction" );
- }
- Object suspendedResources = suspend(transaction);
- boolean newSynchronization = ( this .transactionSynchronization == SYNCHRONIZATION_ALWAYS);
- return newTransactionStatus(
- definition, null , false , newSynchronization, debugEnabled, suspendedResources);
- }
- if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
- if (debugEnabled) {
- logger.debug("Suspending current transaction, creating new transaction with name [" +
- definition.getName() + "]" );
- }
- Object suspendedResources = suspend(transaction);
- doBegin(transaction, definition);
- boolean newSynchronization = ( this .transactionSynchronization != SYNCHRONIZATION_NEVER);
- return newTransactionStatus(
- definition, transaction, true , newSynchronization, debugEnabled, suspendedResources);
- }
- if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
- if (!isNestedTransactionAllowed()) {
- throw new NestedTransactionNotSupportedException(
- "Transaction manager does not allow nested transactions by default - " +
- "specify 'nestedTransactionAllowed' property with value 'true'" );
- }
- if (debugEnabled) {
- logger.debug("Creating nested transaction with name [" + definition.getName() + "]" );
- }
- if (useSavepointForNestedTransaction()) {
- // Create savepoint within existing Spring-managed transaction,
- // through the SavepointManager API implemented by TransactionStatus.
- // Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.
- DefaultTransactionStatus status =
- newTransactionStatus(definition, transaction, false , false , debugEnabled, null );
- status.createAndHoldSavepoint();
- return status;
- }
- else {
- // Nested transaction through nested begin and commit/rollback calls.
- // Usually only for JTA: Spring synchronization might get activated here
- // in case of a pre-existing JTA transaction.
- doBegin(transaction, definition);
- boolean newSynchronization = ( this .transactionSynchronization != SYNCHRONIZATION_NEVER);
- return newTransactionStatus(definition, transaction, true , newSynchronization, debugEnabled, null );
- }
- }
- // Assumably PROPAGATION_SUPPORTS.
- if (debugEnabled) {
- logger.debug("Participating in existing transaction" );
- }
- boolean newSynchronization = ( this .transactionSynchronization != SYNCHRONIZATION_NEVER);
- return newTransactionStatus(definition, transaction, false , newSynchronization, debugEnabled, null );
- }
我們看到這個方法其實很明瞭,就是什麼樣的傳播途徑就創建什麼樣的transactionstatus,這個方法是在事務開始時被調用的,
拿到我們之前舉的例子中來看下,我們就恍然大悟了,原來,如果之前已經創建過事務,那個這個新建的transactionstauts就不應該是屬於一個
newTransaction了,所以第3個參數就是false了。
也就是說,在BService的方法出棧要要執行processcommit,但是由於BService的那個TransactionStatus不是一個newTransaction,所以它根本不會觸發這個動作:
- else if (status.isNewTransaction()) { //這個判斷非常重要,下面會詳細講解這個判斷的作用
- if (status.isDebug()) {
- logger.debug("Initiating transaction commit" );
- }
- boolean globalRollbackOnly = status.isGlobalRollbackOnly();
- doCommit(status);
- }
- else if (status.isNewTransaction()) { //這個判斷非常重要,下面會詳細講解這個判斷的作用
- if (status.isDebug()) {
- logger.debug("Initiating transaction commit" );
- }
- boolean globalRollbackOnly = status.isGlobalRollbackOnly();
- doCommit(status);
- }
也就是說在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 應用程序的分佈式事務中使用自定義隔離級別 |
級別: 中級 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 設計模式和全局/分佈式事務的概念。 軟件應用程序的需求也許做了這樣的規定(這裏的許多技術超出了本文討論範圍),即在執行一個給定用例的過程中,必須將相同的隔離級別使用到所有的數
據訪問中。需求也許還這樣規定,在一個用例實現中只要訪問了兩項或超過兩項的外部資源,該應用程序就應該使用全局事務。例如,作爲用例實現的一部分,應用
程序也許會查詢兩個不同的數據庫表並將一條消息放到消息隊列中。針對這個用例的設計也許需要使用 “已提交讀” 隔離級別來執行兩個數據庫 您可以分別爲兩個
使用 JTA 事務管理器的新手或只對它瞭解一點的開發人員也許想要爲服務對象(如 清單 1. 使用 JTA 事務管理器的事務代理的錯誤定義
清單 1 中定義了兩個 bean。第一個 bean 的定義指定了應用程序將使用的事務管理器。正如您能看到的那樣,這個 bean 依賴於另一個叫做
清單 1
中第二個 bean(稱爲
出現這個錯誤是因爲 JTA 事務管理器不支持自定義隔離級別。當使用 JTA 事務管理器時,事務代理的 bean 定義會和清單 2 中的類似: 清單 2. 使用 JTA 事務管理器的事務代理的正確定義
請注意,和 清單 1
惟一的區別是,現在所有的隔離級別都被設置爲 圖 1 中的序列圖說明了在執行 圖 1. OrderService 實現的 save() 方法的序列圖 從 這裏 看全圖。 在執行
該解決方案是一個由 7 個步驟組成的過程,在此過程中利用了名爲
您也許會回想起之前用 Spring 編寫數據訪問代碼的經歷,可以輕易地通過將一個
清單 3. JdbcOperationsImpl 實例的定義
第二步是要確保所有的數據訪問對象使用
請將
現在, 清單 4. JdbcOperationsImpl 類中 getJdbcTemplate() 方法的實現
在這段代碼中, 清單 5. JdbcOperationsImpl 類的構造函數
從清單 6 中可見,只要要求應用程序的上下文返回標識爲 清單 6. jdbcTemplate bean 的定義
第三步是用 清單 6
顯示的定義更新 Spring 配置文件,並定義 清單 7. IsolationLevelUtil 類的實現
如果您正在思考哪個組件將相應的 清單 8. IsolationLevelAdvice 類的實現
在該應用程序中,每個服務對象實現都需要此類的實例。
第五步是要在 Spring 配置文件中定義這個類的一個 bean 定義,該 bean 將和 清單 9.針對 OrderService 實現的隔離 advice bean 的定義
清單 9 中 bean 的定義顯示了
第六步是要指定 清單 10. 針對 OrderService 實現的 AOP 代理 bean 的定義
第七步也是最後的一步是要定義應用程序所需的 清單 11. JdbcTemplate 和數據源對象的定義
圖 2 中的類圖擷取了這些類中存在的關係,定義這些類是爲了實現我所描述過的解決方案: 圖 2. 本文解決方案的類圖 在 這裏 查看全圖。 在這個類圖中顯示的大多數關係並沒有定義在 Java 源代碼中,而是在 Spring 配置文件中。(這對 Spring 用戶來說並不奇怪。)同樣,如果將我探討過的 Spring bean 的定義和該類圖中的實體作比較,很容易看出,在 圖 2
中被標識爲 下載 這些類的完整的源代碼,您需要這些類來實現我在本文中演示的解決方案。 |