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

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

 

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

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


如下是事務的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個傳播級別,在平常應用中,一般能夠知足各類業務需求,可是除了傳播級別,在讀取數據庫的過程當中,若是兩個事務併發執行,那麼彼此之間的數據是如何影響的呢?

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

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


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

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

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

四、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 致使事務的異常被吃掉而不能正常回滾。express

 

 

 

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>session

相關文章
相關標籤/搜索