《Spring3實戰》摘要(6)事務管理

第6章 事務管理

6.1 理解事務

在軟件開發領域,全有或全無的操作被稱爲事務(transaction)。事務允許你將幾個操作組合成一個要麼全部發生要麼全部不發生的工作單元。如果一切順利,事務將會成功。但是有任何一件事情出錯的話,所發生的行爲將會被清除乾淨,就像什麼事情都沒有發生一樣。

6.1.1 用4個詞來表示事務

在傳統的軟件開發中,人們創建了一個術語來描述事務:ACID。簡單來說,ACID 表示4個特性。

  • 原子性(Atomic):事務是由一個或多個活動所組成的一個工作單元。原子性確保事務中的所有操作全部發生或全部不發生。如果所有的活動都成功了,事務也成功了。如果任意一個活動失敗了,整個事務也失敗並回滾。
  • 一致性(Consistent):一旦事務完成(不管成功還是失敗),系統必須確保它所建模的業務處於一致的狀態 。現實的數據不應該被損壞。
  • 隔離性(Isolated):事務允許多個用戶對相同的數據進行操作,每個用戶的操作不會與其他用戶糾纏在一起。因此,事務應該被彼此隔離,避免發生同步讀寫相同數據的事情(注意的是,隔離性往往涉及到鎖定數據庫中的行或表)。
  • 持久性(Durable):一旦事務完成,事務的結果應該持久化,這樣就能從任何的系統崩潰中恢復過來。這一般涉及將結果存儲到數據庫或其他形式的持久化存儲中。

6.1.2 理解 Spring 對事務管理的支持

如果你的應用程序只使用一種持久化資源,Spring 可以使用持久化機制本身所提供的事務性支持,這包括了 JDBC、Hibernate 以及 Java 持久化 API(JPA)。但是如果應用程序的事務跨多個資源,那麼Spring會使用第三方的 JTA(Java 事務API,Java Transaction API)實現來支持分佈式(XA)事務。

編碼式事務允許用戶在代碼中精確定義事務的邊界,而聲明式事務(基於AOP)有助於用戶將操作與事務規則進行解耦。

選擇編碼式事務還是聲明式事務很大程度上式在細粒度控制和易用性之間進行權衡。當通過編碼實現事務控制時,你能夠精確控制事務的邊界,它們的開始和結束完全取決於你的需求。通常,你不需要編碼式事務所提供的細粒度控制,而會選擇在上下文定義文件中聲明事務。

6.2 選擇事務管理器

Spring 並不直接管理事務,而是提供了多種事務管理器,它們將事務管理的職責委託給 JTA 或其他持久化機制所提供的平臺相關的事務實現。

這裏寫圖片描述

爲了使用事務管理器,你需要將其聲明在應用程序上下文中。

6.2.1 JDBC 事務

將 DataSourceTransactionManager 裝配到應用程序上下文定義中:

<bean id="transactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!-- dataSource 屬性爲 javax.sql.DataSource Bean -->
            <property name="dataSource" ref="dataSource" />
</bean>

在幕後,DataSourceTransactionmanager 通過調用 Java.sql.Connection 來管理事務,而後者是通過 DataSource 獲取到的。通過調用連接的 commit() 方法來提交事務。同樣,事務失敗時通過調用 rollback() 方法進行回滾。

6.2.2 Hibernate 事務

對於 Hibernate 3 ,需要在 Spring 上下文定義中添加如下的聲明:

<bean id="transactionManager" 
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>

HibernateTransactionManager 將事務管理的職責委託給 org.hibernate.Transaction 對象,而後者是從 Hibernate Session 中獲取到的。當事務成功完成時,HibernatetransactionManager 將會調用 Transaction 對象的 commit() 方法。類似地,如果事務失敗,Transaction 對象的 rollback() 方法將會被調用。

6.2.3 Java持久化API事務(略)

6.3 在 Spring 中編碼事務

參考Spring事務管理(2)–第五章 編程式事務管理

6.4 聲明式事務

Spring 提供了3種方式來聲明事務式邊界。以前,Spring 只能使用 Spring AOP 和 TransactionProxyFactoryBean 的代理 Bean 來實現聲明式事務。但是自從 Spring 2.0 ,聲明事務的更高方式是使用 Spring 的 tx 命名空間和 @Transactional 註解。

在最新版本的 Spring 中,儘管遺留的 TransactionProxyFactoryBean 仍然可以使用,但它實際上已經被淘汰了。具體用法可參考Spring事務管理(2)–第六章 聲明式事務管理

在本小節的後面,我們將會關注 tx 命名空間和以註解爲主的聲明式事務。

6.4.1 定義事務屬性

在 Spring 中,聲明式事務式通過事務屬性(transaction attribute)來定義的。事務屬性描述了事務策略如何應用到方法上。事務屬性包括了5個方面:

  • 傳播行爲(propagation behavior)
  • 隔離級別(isolation level)
  • 回滾規則
  • 事務超時
  • 是否只讀

傳播行爲(propagation behavior)
傳播行爲定義了客戶端與被調用方法之間的事務邊界。Spring 定義了7種不同的傳播行爲。

傳播行爲都在 org.springframework.transaction.TransactionDefinition 接口中以常量的方式進行了定義。

傳播行爲 含義
PROPAGATION_MANDATORY 表示該方法必須在事務中運行,如果當前事務不存在,則會拋出一個異常
PROPAGATION_NESTED 表示如果當前已經存在一個事務,那麼該方法將會在嵌套事務中運行。嵌套的事務可以獨立於當前事務進行單獨地提交或回滾。如果當前事務不存在,那麼其行爲與PROPAGATION_REQUIRED一樣。注意各廠商對這種傳播行爲的支持是有所差異的。可以參考資源管理器文檔來確定它們是否支持嵌套式事務。
PROPAGATION_NEVER 表示當前方法不應該運行在事務上下文中。如果當前正有一個事務在運行,則拋出異常。
PROPAGATION_NOT_SUPPORTED 表示該方法不應該運行在事務中。如果存在當前事務,在該方法運行期間,當前事務將被掛起。如果使用 JTATransactionManager 的話,則需要訪問 TransactionManager。
PROPAGATION_REQUIRED 表示當前方法必須運行在事務中。如果當前事務存在,方法將會在該事務中運行。否則,會啓動一個新的事務。
PROPAGATION_REQUIRES_NEW 表示當前方法必須運行在它的事務中。一個新的事務將被啓動。如果存在當前事務,在該方法執行期間,當前事務會被掛起,如果使用 JTATransactionManager,則需要訪問 TransactionManager
PROPAGATION_SUPPORTS 表示當前方法不需要事務上下文,但是如果存在當前事務的話,那麼該方法會在這個事務中運行。

隔離級別(isolation level)

隔離級別定義了一個事務可能受其他併發事務影響的程度。另一種考慮隔離級別的方式就是將其想想成事務對於事務性數據的自私程度。

隔離級別都在 org.springframework.transaction.TransactionDefintion 接口中以常量的方式進行了定義。

在典型的應用程序中,多個事務併發運行,經常會操作相同的數據來完成各自的任務。併發,雖然是必需的,但可能會導致一下問題:

  • 髒讀(Dirty reads):髒讀發生在一個事務讀取了另一個事務改寫但是尚未提交的數據時。如果改寫在稍後被回滾了,那麼第一個事務獲取的數據就是無效的。
  • 不可重複讀(Nonrepeatable read):不可重複讀發生在一個事務執行相同的查詢兩次或兩次以上,但是每次都得到不同的數據時。這通常是因爲另一個併發事務在兩次查詢期間更新了數據。
  • 幻讀(Phantom read):幻讀與不可重複讀類似。它發生在一個事務(T1)讀取了幾行數據,接着另一個併發事務(T2)插入了一些數據時。在隨後的查詢中,第一個事務(T1)就會發現多了一些原本不存在的記錄。

在理想情況下,事務之間是完全隔離的,從而可以防止這些問題發生。但考慮到完全的隔離會導致性能問題,而且並不是所有的應用程序都需要完全的隔離,所以有時應用程序需要在事務隔離上有一定的靈活性。因此,就會有多種隔離級別。

隔離級別 含義
ISOLATION_DEFAULT 使用後端數據庫默認的隔離級別
ISOLATION_READ_UNCOMMITTED 允許讀取尚未提交的數據變更,可能會導致髒讀、幻讀或不可重複讀,是事務隔離程度最低的隔離級別
ISOLATION_READ_COMMITTED 允許讀取併發事務已經提交的數據。可以組織髒讀,但是幻讀或不可重複讀仍有可能發生
ISOLATION_REPEATABLE_READ 對同一個字段的多次讀取結果是一致的,除非數據是被本事務所修改,可以組織髒讀和不可重複讀,但幻讀仍有可能發生
ISOLATION_SERIALIZABLE 完全服從ACID的隔離級別,確保阻止髒讀、不可重複讀以及幻讀。這是最慢的事務隔離級別,因爲它通常是通過完全鎖定事務相關的數據庫來實現的

只讀
如果事務只對後端的數據庫進行只讀操作,數據庫可以利用事務的只讀特性來進行一些特定的優化。通過將事務設置爲只讀,你就可以給數據庫一個機會,讓它應用它認爲合適的優化措施。

因爲只讀優化是在事務啓動的時候由數據庫實施的,只有對那些具備啓動一個新事務的傳播行爲(PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW 以及 PROPAGATION_NESTED
)的方法來說,將事務聲明爲只讀纔有意義。

另外,如果採用 Hibernate 作爲持久化機制,那麼僵事務聲明爲只讀會導致 Hibernate 的 flush 模式被設置爲 FLUSH_NEVER。這會告訴 Hibernate 避免和數據庫進行不必要的對象同步,並將所有的更新延遲到事務結束。

事務超時
爲了使應用程序很好地運行,事務不能運行太長的時間。假設事務的運行時間變得特別長,因爲事務可能涉及對後端數據庫的鎖定,所以長時間的事務會不必要地佔用數據庫資源。你可以聲明一個事務,在特定的秒數後自動回滾,而不是等待其結束。

因爲超時時鐘會在事務開始時啓動,所以,只有對那些具備可能啓動一個新事務的傳播行爲(PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW 以及 PROPAGATION_NESTED)的方法來說,聲明事務超時纔有意義。

回滾規則
回滾規則定義了哪些異常會導致事務回滾而哪些不會。默認情況下,事務只有在遇到運行期異常時纔會回滾,而在遇到檢查型異常時不會回滾(這一行爲與EJB的回滾行爲是一致的)

6.4.2 在 XML 中定義事務

簡單樣例參考
Spring 提供了一個 tx 配置命名空間,藉助它可以極大地簡化 Spring 中的聲明式事務。使用 tx 命名空間會涉及將其添加到 Spring XML 配置文件中:

<?xml version="1.0" encoding="UTF-8"?>
<!-- aop 命名空間也應該包括在內,因爲有一些聲明式事務配置元素依賴於部分 Spring 的 AOP 配置元素 -->
<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd" >

這個 tx 命名空間提供了一些新的 XML 配置元素,其中,<tx:advice> 用於聲明事務性策略。

<!-- 未顯示聲明配置事務管理器Bean,默認配置 id 爲 transactionManager 的 Bean -->
<tx:advice id="txAdvice">
    <tx:attributes>
        <!-- 聲明名稱以 save 開頭的方法需要事務 -->
        <tx:method name="save*" propagation="REQUIRED"/>
        <!-- 其他方法聲明如果存在當前事務,它們將會在事務中運行,但是它們並不要求必須在事務中運行 -->
        <tx:method name="*" propagation="SUPPORTS" read-only="true"/>
    </tx:attributes>
</tx:advice>

對於<tx:advice>來說,事務屬性定義在<tx:attributes>元素中,該元素包含了一個或多個的<tx:method>元素。<tx:method>元素爲某個(或某些)name 屬性(使用通配符)指定的方法定義事務參數。

<tx:method>有多個屬性來幫助定義方法的事務策略:

屬性 含義
isolation 指定事務的隔離級別
propagation 定義事務的傳播規則
read-only 指定事務的只讀
回滾規則:rollback-for / no-rollback-for 指定事務對於哪些檢查型異常應當回滾而不提交 / 指定事務對於哪些異常應當繼續運行而不回滾
timeout 對於長時間運行的事務定義超時時間

當使用<tx:advice>來聲明事務時,你還需要一個事務管理器。<tx:advice>假定事務管理器被聲明爲一個 id 爲 transactionManager 的 Bean 。如果需要配置 id 名不同的事務管理器,則需要在 transaction-manager 屬性中明確指定事務管理器的 id :

<tx:advice id="txAdvice" transaction-manager="xxx">
    ...
</tx:advice>

<tx:advice>只是定義了 AOP 通知,用於把事務的邊界通知給方法。爲了完整定義事務性切面,我們必須定義一個通知器(advisor)。這就涉及 aop 命名空間了。

以下的 XML 定義了一個通知器:

<!-- 使用 txAdvice 通知所有實現 SpitterService 接口的 Bean -->
<aop:config>
    <aop:advisor advice-ref="txAdvice"
        pointcut="execution(* *..SpitterService.*(..))"/>
</aop:config>

6.4.3 定義註解驅動的事務

除了<tx:advice>元素,tx 命名空間還提供了<tx:annotation-driven> 元素。使用<tx:annotation-driven />時,通常只需要一行 XML。

<tx:annotation-driven> 元素告訴 Spring 檢查上下文中所有的 Bean 並查找使用 @Transactional 註解的 Bean,而不管這個註解是用在類級別上還是方法級別上。

我們可以通過 transaction-manager 屬性來指定特定的事務管理器(不配置默認值爲 transactionManager)

對於每一個使用 @Transactional 註解的 Bean,<tx:annotation-driven> 會自動爲它添加事務通知。通知的事務屬性是通過 @Transactional 註解的參數來定義的。

@Transactional( propagation=Propagation.SUPPORTS, readOnly=true )
public class SpitterServiceImpl implements SpitterService {
    //...
    @Transactional( propagation=Propagation.REQUIRED,readOnly=false )
    public void addSpitter(Spitter spitter){
        //...
    }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章