Spring事務傳播機制與隔離級別

轉自:http://blog.csdn.net/edward0830ly/article/details/7569954 (寫的不錯)

 

事務是邏輯處理原子性的保證手段,通過使用事務控制,可以極大的避免出現邏輯處理失敗導致的髒數據等問題。

事務最重要的兩個特性,是事務的傳播級別和數據隔離級別。傳播級別定義的是事務的控制範圍,事務隔離級別定義的是事務在數據庫讀寫方面的控制範圍。


以下是事務的7種傳播級別:


1) PROPAGATION_REQUIRED ,默認的spring事務傳播級別,使用該級別的特點是,如果上下文中已經存在事務,那麼就加入到事務中執行,如果當前上下文中不存在事務,則新建事務執行。所以這個級別通常能滿足處理大多數的業務場景。


2)PROPAGATION_SUPPORTS ,從字面意思就知道,supports,支持,該傳播級別的特點是,如果上下文存在事務,則支持事務加入事務,如果沒有事務,則使用非事務的方式執行。所以說,並非所有的包在transactionTemplate.execute中的代碼都會有事務支持。這個通常是用來處理那些並非原子性的非核心業務邏輯操作。應用場景較少。


3)PROPAGATION_MANDATORY , 該級別的事務要求上下文中必須要存在事務,否則就會拋出異常!配置該方式的傳播級別是有效的控制上下文調用代碼遺漏添加事務控制的保證手段。比如一段代碼不能單獨被調用執行,但是一旦被調用,就必須有事務包含的情況,就可以使用這個傳播級別。


4)PROPAGATION_REQUIRES_NEW ,從字面即可知道,new,每次都要一個新事務,該傳播級別的特點是,每次都會新建一個事務,並且同時將上下文中的事務掛起,執行當前新建事務完成以後,上下文事務恢復再執行。

這是一個很有用的傳播級別,舉一個應用場景:現在有一個發送100個紅包的操作,在發送之前,要做一些系統的初始化、驗證、數據記錄操作,然後發送100封紅包,然後再記錄發送日誌,發送日誌要求100%的準確,如果日誌不準確,那麼整個父事務邏輯需要回滾。
怎麼處理整個業務需求呢?就是通過這個PROPAGATION_REQUIRES_NEW 級別的事務傳播控制就可以完成。發送紅包的子事務不會直接影響到父事務的提交和回滾。


5)PROPAGATION_NOT_SUPPORTED ,這個也可以從字面得知,not supported ,不支持,當前級別的特點就是上下文中存在事務,則掛起事務,執行當前邏輯,結束後恢復上下文的事務。


這個級別有什麼好處?可以幫助你將事務極可能的縮小。我們知道一個事務越大,它存在的風險也就越多。所以在處理事務的過程中,要保證儘可能的縮小範圍。比如一段代碼,是每次邏輯操作都必須調用的,比如循環1000次的某個非核心業務邏輯操作。這樣的代碼如果包在事務中,勢必造成事務太大,導致出現一些難以考慮周全的異常情況。所以這個事務這個級別的傳播級別就派上用場了。用當前級別的事務模板抱起來就可以了。


6)PROPAGATION_NEVER ,該事務更嚴格,上面一個事務傳播級別只是不支持而已,有事務就掛起,而PROPAGATION_NEVER傳播級別要求上下文中不能存在事務,一旦有事務,就拋出runtime異常,強制停止執行!這個級別上輩子跟事務有仇。


7)PROPAGATION_NESTED ,字面也可知道,nested,嵌套級別事務。該傳播級別特徵是,如果上下文中存在事務,則嵌套事務執行,如果不存在事務,則新建事務。

那麼什麼是嵌套事務呢?很多人都不理解,我看過一些博客,都是有些理解偏差。

嵌套是子事務套在父事務中執行,子事務是父事務的一部分,在進入子事務之前,父事務建立一個回滾點,叫save point,然後執行子事務,這個子事務的執行也算是父事務的一部分,然後子事務執行結束,父事務繼續執行。重點就在於那個save point。看幾個問題就明瞭了:

如果子事務回滾,會發生什麼? 

父事務會回滾到進入子事務前建立的save point,然後嘗試其他的事務或者其他的業務邏輯,父事務之前的操作不會受到影響,更不會自動回滾。


如果父事務回滾,會發生什麼? 

父事務回滾,子事務也會跟着回滾!爲什麼呢,因爲父事務結束之前,子事務是不會提交的,我們說子事務是父事務的一部分,正是這個道理。那麼:


事務的提交,是什麼情況? 

是父事務先提交,然後子事務提交,還是子事務先提交,父事務再提交?答案是第二種情況,還是那句話,子事務是父事務的一部分,由父事務統一提交。


現在你再體會一下這個”嵌套“,是不是有那麼點意思?


以上是事務的7個傳播級別,在日常應用中,通常可以滿足各種業務需求,但是除了傳播級別,在讀取數據庫的過程中,如果兩個事務併發執行,那麼彼此之間的數據是如何影響的呢?

這就需要了解一下事務的另一個特性:數據隔離級別

數據隔離級別分爲不同的四種:


1、Serializable :最嚴格的級別,事務串行執行,資源消耗最大;

2、REPEATABLE READ :保證了一個事務不會修改已經由另一個事務讀取但未提交(回滾)的數據。避免了“髒讀取”和“不可重複讀取”的情況,但是帶來了更多的性能損失。

3、READ COMMITTED :大多數主流數據庫的默認事務等級,保證了一個事務不會讀到另一個並行事務已修改但未提交的數據,避免了“髒讀取”。該級別適用於大多數系統。

4、Read Uncommitted :保證了讀取過程中不會讀取到非法數據。
 
上面的解釋其實每個定義都有一些拗口,其中涉及到幾個術語:髒讀、不可重複讀、幻讀。
這裏解釋一下:
 
髒讀 :所謂的髒讀,其實就是讀到了別的事務回滾前的髒數據。比如事務B執行過程中修改了數據X,在未提交前,事務A讀取了X,而事務B卻回滾了,這樣事務A就形成了髒讀。
 
不可重複讀 :不可重複讀字面含義已經很明瞭了,比如事務A首先讀取了一條數據,然後執行邏輯的時候,事務B將這條數據改變了,然後事務A再次讀取的時候,發現數據不匹配了,就是所謂的不可重複讀了。
 
幻讀 :小的時候數手指,第一次數十10個,第二次數是11個,怎麼回事?產生幻覺了?
幻讀也是這樣子,事務A首先根據條件索引得到10條數據,然後事務B改變了數據庫一條數據,導致也符合事務A當時的搜索條件,這樣事務A再次搜索發現有11條數據了,就產生了幻讀。
 
一個對照關係表:
                                       Dirty reads          non-repeatable reads            phantom reads
Serializable                          不會                        不會                                           不會
REPEATABLE READ             不會                        不會                                            會
READ COMMITTED             不會                        會                                                會
Read Uncommitted             會                           會                                                會
 
所以最安全的,是Serializable,但是伴隨而來也是高昂的性能開銷。
另外,事務常用的兩個屬性:readonly和timeout
一個是設置事務爲只讀以提升性能。
另一個是設置事務的超時時間,一般用於防止大事務的發生。還是那句話,事務要儘可能的小!

最後引入一個問題:
一個邏輯操作需要檢查的條件有20條,能否爲了減小事務而將檢查性的內容放到事務之外呢? 

很多系統都是在DAO的內部開始啓動事務,然後進行操作,最後提交或者回滾。這其中涉及到代碼設計的問題。小一些的系統可以採用這種方式來做,但是在一些比較大的系統,
邏輯較爲複雜的系統中,勢必會將過多的業務邏輯嵌入到DAO中,導致DAO的複用性下降。所以這不是一個好的實踐。


來回答這個問題:能否爲了縮小事務,而將一些業務邏輯檢查放到事務外面?答案是:對於核心的業務檢查邏輯,不能放到事務之外,而且必須要作爲分佈式下的併發控制!
一旦在事務之外做檢查,那麼勢必會造成事務A已經檢查過的數據被事務B所修改,導致事務A徒勞無功而且出現併發問題,直接導致業務控制失敗。
所以,在分佈式的高併發環境下,對於核心業務邏輯的檢查,要採用加鎖機制。
比如事務開啓需要讀取一條數據進行驗證,然後邏輯操作中需要對這條數據進行修改,最後提交。
這樣的一個過程,如果讀取並驗證的代碼放到事務之外,那麼讀取的數據極有可能已經被其他的事務修改,當前事務一旦提交,又會重新覆蓋掉其他事務的數據,導致數據異常。
所以在進入當前事務的時候,必須要將這條數據鎖住,使用for update就是一個很好的在分佈式環境下的控制手段。

一種好的實踐方式是使用編程式事務而非生命式,尤其是在較爲規模的項目中。對於事務的配置,在代碼量非常大的情況下,將是一種折磨,而且人肉的方式,絕對不能避免這種問題。
將DAO保持針對一張表的最基本操作,然後業務邏輯的處理放入manager和service中進行,同時使用編程式事務更精確的控制事務範圍。
特別注意的,對於事務內部一些可能拋出異常的情況,捕獲要謹慎,不能隨便的catch Exception 導致事務的異常被吃掉而不能正常回滾。

 

 

 

Spring配置聲明式事務:
* 配置SessionFactory
* 配置事務管理器
* 事務的傳播特性
* 那些類那些方法使用事務

 

編寫業務邏輯方法
* 繼承HibernateDaoSupport類,使用HibernateTemplate來持久化,HibernateTemplate是
   hibernate Session的輕量級封裝
* 默認情況下運行期異常纔會回滾(包括繼承了RuntimeException子類),普通異常是不會滾的
* 編寫業務邏輯方法時,最好將異常一直向上拋出,在表示層(struts)處理
* 關於事務邊界的設置,通常設置到業務層,不要添加到Dao上

 

<!-- 配置SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    <property name="configLocation">
     <value>classpath:hibernate.cfg.xml</value>
    </property>
</bean>

<!-- 配置事務管理器 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

<!-- 事務的傳播特性 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
     <tx:method name="add*" propagation="REQUIRED"/>
     <tx:method name="del*" propagation="REQUIRED"/>
     <tx:method name="modify*" propagation="REQUIRED"/>
     <tx:method name="*" propagation="REQUIRED" read-only="true"/>
    </tx:attributes>
</tx:advice>

<!-- 哪些類哪些方法使用事務 -->
<aop:config>
    <aop:pointcut expression="execution(* com.service.*.*(..))" id="transactionPC"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="transactionPC"/>
</aop:config>

<!-- 普通IOC注入 -->
<bean id="userManager" class="com.service.UserManagerImpl">
    <property name="logManager" ref="logManager"/>
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>
<bean id="logManager" class="com.service.LogManagerImpl">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

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