轉自:http://www.tkk7.com/msmary/articles/155398.html
本章是第四章的延續,作者向讀者展示了如何使用Spring事務管理來保證數據一致性。Spring對事務的管理有豐富的支持,程序控制的和聲明式的都有。在本章中,我們會學習到如何把應用程序的代碼放置在事務中,以確保在一切順利時,所有的成果都被固定下來;一旦其中有一步出錯,那么整個事情就像沒有發生一樣。
一、理解事務
首先我們應該弄清楚什么是事務,這樣才能認識到事務的重要性。舉一個小小的例子,大概每個人都有轉賬的經歷。當我們從A帳戶向B帳戶轉100元后,銀行的系統會從A帳戶上扣除100而在B帳戶上加100,這是一般的正常現象。但是一旦系統出錯了怎么辦呢,這里我們假設可能會發生兩種情況:(1)A帳戶上少了100元,但是B帳戶卻沒有多100元。(2)B帳戶多了100元錢,但是A帳戶上卻沒有被扣錢。這種錯誤一旦發生就等于出了大事,那么再假如一下,你要轉賬的是1億呢?所以上面的兩種情況分別是你和銀行不愿意看到的,因為誰都不希望出錯。那么有沒有什么方法保證一旦A帳戶上沒有被扣錢而B帳戶上也沒有被加錢;或者A帳戶扣了100元而B帳戶準確無誤的加上100元呢。也就是說要么轉賬順利的成功進行,要么不轉賬呢?可以,這就是數據庫事務機制所要起到的作用和做的事情。
下面給出事務的概念(在數據庫中事務的概念在表述上略有差異,但是含義都是一樣的):
所謂事務是用戶定義的一個數據庫操作序列。這些操作要么都做,要么都不做,是一個不可分割的工作單位。事務具有四個特性:
1. 原子性:一個事務中所有對數據庫的操作是一個不可分割的操作序列。這些操作要么完整的被全部執行,要么一步也不做。是一個邏輯工作單位。
2. 一致性:一個事務獨立執行的結果將保持一致性,即數據不會因為事務的執行而遭受破壞。
3. 隔離性:一個事務的執行不能被其他事務干擾。即一個事務內部的操作及使用的數據對其他并發事務是隔離的,并發執行的各個事務之間不能互相干擾。
4. 持久性:一個事務一旦提交,它對數據庫中數據的改變就應該是永久性的。接下來的其他操作或故障不應該對其執行結果有任何影響。
結合前面的例子解釋一下概念:在轉賬示例中A扣100元錢和B加100元錢這兩步更新操作就是一個操作序列。這兩步是一個整體要么全部成功,要么全部成功,絕對不能只成功一步而另一步失敗。關于數據庫事務的詳細知識請參考有關的數據庫原理書籍。
Spring框架引人注目的重要因素之一是它全面的事務支持。Spring支持“編程式事務”和“聲明式事務”
二、在Spring中編寫事務
在我們的代碼中添加事務的一種方法就是使用Spring的TransactionTemplate類,用程序添加事務邊界。就像其他Spring的模版類一樣,TransactionTemplate利用一套回調機制,把我們的代碼包裝在一個TransactionTemplate里。看下面的代碼片斷:
publicvoid enrollStudentInCourse()
{
tran.execute(new TransactionCallback()
{
public Object doInTransaction(TransactionStatus ts){
try{
//do something....
}catch(Exception ex){
ts.setRollbackOnly();//出錯進行事務回滾
}
returnnull;
}
}
);
}
下面是配置文件:
<bean id = "transactionTemplate" class = "org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager"><ref bean = "transactionTemplate"/></property>
</bean>
<bean id = "courseService" class = "MyImp">
<property name="transactionTemplate"><ref bean = "transactionTemplate"/></property>
</bean>
transactionTemplate對象有一個transactionManager屬性。該屬性為PlatformTransactionManager類型,它用來處理特定平臺的事務管理的細節。也可以直接使用 org.springframework.transaction.PlatformTransactionManager
的實現來管理事務。只需通過bean引用簡單地傳入一個 PlatformTransactionManager
實現,然后使用 TransactionDefinition
和 TransactionStatus
對象,你就可以啟動一個事務,提交或回滾。在上例的簡單代碼中我們展示的是“編程式事務”當你完全希望控制事務的邊界時候,“編程式事務”是不錯的。但是他有些侵入性。通常我們的事務需求并沒有要求在事務的邊界上進行如此精確的控制。我們可以使用“聲明式事務”。
三、聲明式事務
大多數Spring用戶選擇聲明式事務管理。這是對應用代碼影響最小的選擇,因此也最符合非侵入式輕量級容器的理念。Spring的聲明式事務管理是通過Spring AOP實現的,因為事務方面的代碼與Spring綁定并以一種樣板式風格使用,這樣做是非常自然的,因為事務時系統級的,凌駕于應用的主要功能之上的,你可以把Spring的事務想象成是一個切面(方面)包裹著一個方法。不過盡管如此,你一般并不需要理解AOP概念就可以有效地使用Spirng的聲明式事務管理。
在Spring2.0之前要在Spring中使用聲明式事務,得用TransactionProxyFactoryBean。這是一個接口,他有點類似在第三章中的ProxyFactoryBean,只不過它的目的是將方法包裝在事務的上下文中。Spring2.0及以后的版本中聲明式事務的配置與之前的版本有相當大的不同。主要差異在于不再需要配置TransactionProxyFactoryBean
了。Spring2.0之前的舊版本風格的配置仍然是有效的;你可以簡單地認為新的<tx:tags/>
替你定義了TransactionProxyFactoryBean
。和TransactionProxyFactoryBean配合的還有兩個,一個是PlatformTransactionManager另一個是TransactionAttributeSource。
1.理解事務屬性:
在Spring里,事務屬性是對事務策略如何應用到方法的描述。這個描述包括下列一些參數:傳播行為、隔離級別、只讀提示、事務超時間隔。
(1)傳播行為
傳播行為定義了關于客戶端和被調用方法的事務邊界。Spring定義了7種截然不同的傳播行為。(在書中165頁有詳細列表)Spring的傳播行為分別對應了EJB容器管理的事務(CMT)中所有的傳播規則。傳播規則回答了一個問題,就是新的事務是否要被啟動或是被掛起,或者方法是否要在事務環境中運行。
(2)隔離級別
在一個典型的應用中,多個事務并發運行,經常會操作同一個數據來完成任務。并發會導致以下問題:
l 臟讀——臟讀發生在一個事務讀取了被另一個事務改寫但還未提交的數據時。
l 不可重復讀——不可重復讀發生在一個事務執行相同的查詢兩次或兩次以上,但每一次查詢結果不同時。
l 幻讀——幻讀和不可重復讀相似。
在理想狀態下,事務要完全相互隔離,以防止這些問題發生。(這些內容在數據庫原理里面都有講解,大家可以參考數據庫原理的相關書)然而完全隔離會影響性能,因為隔離經常牽扯到鎖定在數據庫中的記錄。侵占性的鎖定會阻礙并發,要求事務相互等待來完成工作,這樣會引起“事務死鎖”。
應該認識到完全隔離會影響性能,并且不是所有應用都需要完全隔離,有時候應用需要在事務隔離上有些彈性。因此也有好幾個隔離級別,見書166頁下面的表格。
(3)只讀
如果一個事務只對后段數據庫執行讀操作,數據庫就可能利用事務只讀的特性,使用某些優化措施。通過聲明一個事務為只讀,你就給了后端數據庫一個機會,來應用那些它認為合適的優化措施。
(4)事務超時
在事務中你可能選擇設置的另一個屬性就是超時。假設你的事務在預料之外長時間運行,因為事務可能涉及對后端數據庫的鎖定,長時間運行的事務會不必要的占用數據庫資源。與其等他結束,不如聲明一個事務,在特定秒數后自動回滾。
四、通過方法名聲明事務
NameMatchTransactionAttributeSource,這個事務屬性源讓你在方法名的基礎上聲明事務屬性。看下面的例子:
<bean id = "transactionAttuibuteSource" class = "org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource">
<property name="properties">
<props>
<prop key="enrollStudentInCourse">
PROPAGATION_REQUIRES_NEW
</prop>
</props>
</property>
</bean>
被命名為transactionAttuibuteSource的bean 將被置入到TransactionProxyFactoryBean的transactionAttuibuteSource屬性中。
NameMatchTransactionAttributeSource的properties屬性把方法名映射到事務屬性描述器上。事務屬性描述器有以下幾種形式:
l PROPAGATION——傳播行為
l ISOLATION——隔離級別(可選)
l Readonly——是只讀事務嗎?(可選)
l -Exeptions——回滾規則(可選)
l +Exception——同上
在上面的例子中,只指定了傳播行為。但是,事務的許多其他參數都可以在事務屬性描述器中定義。
指定事務隔離級別
<bean id = "transactionAttuibuteSource" class = "org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource">
<property name="properties">
<props>
<prop key="enrollStudentInCourse">
PROPAGATION_REQUIRES_NEW, ISOLATION_REPEATABLE_READ
</prop>
</props>
</property>
</bean>
使用只讀事務
<bean id = "transactionAttuibuteSource" class = "org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource">
<property name="properties">
<props>
<prop key="enrollStudentInCourse">
PROPAGATION_REQUIRES_NEW, ISOLATION_REPEATABLE_READ, readOnly
</prop>
</props>
</property>
</bean>
指定回滾規則
當事務運行過程中拋出異常時,事務可以被聲明為回滾或者不回滾。默認情況下,發生運行時間異常時,事務將被回滾;發生受查異常(checked exception)時,不回滾。
<bean id = "transactionAttuibuteSource" class = "org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource">
<property name="properties">
<props>
<prop key="enrollStudentInCourse">
PROPAGATION_REQUIRES_NEW, ISOLATION_REPEATABLE_READ, -CourseException
</prop>
</props>
</property>
</bean>
CourseException用一個負號(-)標記。異常可以用負號(-)或者正號(+)標記,當負號異常拋出時,將觸發回滾;相反的,正號異常表示事務仍可提交,即使這個異常拋出。你甚至可以把運行時間異常標記為正號,以防止回滾。
使用通配符匹配
<bean id = "transactionAttuibuteSource" class = "org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource">
<property name="properties">
<props>
<prop key="get*">
PROPAGATION_SUPPORTS
</prop>
</props>
</property>
</bean>
五、用元數據聲明事務
就元數據而言,他們不直接改變你的代碼行為。反之,他們給應用的支撐平臺提供提示和建議,指導平臺如何才能把附加行為運用到應用中。用元數據來表現事務屬性是最自然不過的了。正如你所看到的,事物屬性不直接改變方法的執行,但當一個方法被TransactionProxyFactoryBean代理時,它將被包裹在一個事務中。
1.用元數據來書寫事務屬性
為了讓TransactionProxyFactoryBean能夠從元數據檢索事務屬性,需要將他的transactionAttributeSource設置成AttributesTransactionAttributeSource,如下所示:
<bean id = "transactionAttributeSource" class = "org.springframework.transaction.interceptor.AttributesTransactionAttributeSource">
<constructor-arg>
<ref bean = "attributesImpl"/>
</constructor-arg>
</bean>
在這個事務屬性源的構造參數中置入了一個名為attributesImpl的對象的引用。這個attributesImpl對象將被事務屬性源使用,和底層的元數據實現配合。
2.用Commons Attributes聲明事務
Jakarta Commons Attributes是最早在Java中實現元數據的方法之一。Commons Attributes的好處就在于不需要跳到Java 5才能使用元數據。這樣如果你仍然部署在一個比較老的Java版本,并且想用元數據聲明事務的話,唯一的選擇就是使用Commons Attributes。然而,現在Java 6已經快出來了。Java 5已經可以說得到普遍應用了,尤其是在我們公司以及我個人都已經遷移到Java 5平臺上了。所以,運用Commons Attributes聲明事務這里就不多摘錄了。
六、修剪事務聲明
Spring提供兩種方法抗擊繁復的XML:
l Bena繼承
l AOP自動代理
1.從父TransactionProxyFactoryBean繼承
簡化事務和服務對象聲明的一種方法是使用Spring對父bean的支持。使用<bean>標簽的parent屬性,就能夠指定一個bean成為其他bean的孩子,繼承父bean的屬性。
使用bean繼承來縮減包含多重TransactionProxyFactoryBean聲明的XML,開始于在上下文定義中添加一個TransactionProxyFactoryBean的abstract聲明:
<bean id = "abstractTxDefinition" class = "org.springframework.transaction.interceptor.TransactionProxyFactoryBean" lazy-init="true">
<property name="transactionManager">
<ref bean="transactionManager"/>
</property>
<property name="transactionAttributeSource">
<ref bean="attributeSource"/>
</property>
</bean>
這個聲明類似于前面的courseService聲明,除了這兩件事情:
l 首先,它的lazy-init屬性是設置為true的。應用上下文通常在啟動的時候實例化所有singleton bean。既然我們的應用僅僅使用abstractTxDefinition的子bean,而不會直接使用abstractTxDefinition,那么我們就不希望容器去初始化一個我們從來不用的bean。Lazy-init屬性告訴容器不要創建bean,除非我們請求。實際上lazy-init就是把這個bean變成抽象的。
l Traget屬性奇怪的消失了。
下面創建子bean:
<bean id = "courseService" parent = "abstractTxDefinition">
<property name="target">
<bean class = "myImpl">
</property>
</bean>
Parent屬性表示這個bean將從abstractTxDefinition bean中繼承他的定義。這個bean所要添加的一件事情就是綁定一個target屬性的值。
到目前為止,這個技術并沒有讓我們節省更多的XML,但是考慮到你將要做的事情是讓另一個bean事務化,你只需要加另一個abstractTxDefinition的子bean就可以了。
<bean id = "studentService" parent = "abstractTxDefinition">
<property name="target">
<bean class = "myImpl">
</property>
</bean>
但是,請注意你并不用再完整的定義另外一個TransactionProxyFactoryBean。現在想象一下,假如你的應用有幾十個服務bean需要被事務化。當我們有大量需要被事務化的bean時,bean的繼承就真正得到回報了。
2.自動代理事務
在Spring中事務是基于AOP的,同樣可以用自動代理來除掉TransactionProxyFactoryBean的重復實例。
首先,就像要做任何自動通知(auto-advising)一樣,你需要聲明一個bean,成為DefaultAopProxyFactory的實例:
<bean id = "autoproxy" class = "org.springframework.aop.framework.DefaultAopProxyFactory">
....
</bean>
DefaultAopProxyFactory將在應用上下文中遍歷advisor,自動用它來代理匹配advisor的pointcut的所有bean。對于事務,應該使用的advisor是TransactionAttributeSourceAdvisor:
<bean id="transactionAdvisor" class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
<constructor-arg>
<ref bean = "transactionInterceptor"/>
</constructor-arg>
</bean>
TransactionAttributeSourceAdvisor是一個羽翼豐滿的AOP advisor,和任何advisor一樣,它由一個pointcut和一個攔截器組成。這個pointcut是一個static的pointcut。他根據事務屬性源來確定一個方法是否和一些事務屬性關聯。如果一個方法有事務屬性,那么這個方法將被代理并包含在一個事務中。
關于攔截器,它通過一個構造參數置入到TransactionAttributeSourceAdvisor。他由TransactionInterceptor類實現并像下面那樣被置入到應用中:
<bean id = "transactionInterceptor" class = "org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager">
<ref bean = "transactionManager"/>
</property>
<property name="transactionAttributeSource">
<ref bean = "transactionAttributeSource"/>
</property>
</bean>
TransactionInterceptor有兩個用來完成工作的合作者。他使用PlatformTransactionMananager,并將他置入到transactionManager屬性,是事物和他背后的事務實現相配合。同時,他使用被置入到transactionAttributeSource屬性的事務實現源來確定它將攔截的應用刀這個方法的事務策略。事實上,當在使用TransactionProxyFactoryBean時早已定義好了transactionManager和transactionAttributeSource bean——它們也將很好地為事務攔截器工作。
最后一件要做的事情就是刪除所有TransactionProxyFactoryBean的實例,并把服務bean重命名回它們原來正確的名稱。
為自動代理選擇一個屬性源
當使用自動代理事務時,事務屬性源是一個方法是否需要代理的關鍵。這個事務可能會提示你選擇不同的事務屬性源。
當使用自動代理時,對于事務屬性源的一個更好的選擇是MethodMapTransactionAttributeSorrce。這個事務屬性源類似于NameMatchTransactionAttributeSource,但他讓你指定需要事務化的完整類名稱和方法名。
當用自動代理時,事務屬性源的一個更好的選擇是TransactionAttributeSourceAdvisor。回憶一下,AttributeTransactionAttributeSourceAdvisor把事務屬性從元數據中取出來,而這些元數據是直接放在要事務化的方法的代碼中的。這就意味著,如果你使用AttributeTransactionAttributeSourceAdvisor作為屬性源,并同時使用自動代理的話,使方法事務化,只不過是添加合適的元數據到方法里而已。
七、小結
事務是企業應用開發的重要組成部分,他使軟件更加可靠。它們確保一種要么全有要么全無的行為,防止數據不一致而導致的不可預測的錯誤發生。它們同時也支持并發,防止并發應用線程在操作同一數據時互相影響。
關于Spring事務的詳細內容,在Spring2.0文檔中做了詳細的講解。并且在Spring2.0中有了許多改動。