Spring事務配置及事務的傳播性與隔離級別詳解

Spring事務配置

通過註解配置

1.在Spring配置文件引入

<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xmlns:context="http://www.springframework.org/schema/context"
        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
               http://www.springframework.org/schema/context
               http://www.springframework.org/schema/context/spring-context-3.0.xsd">

2.配置基於註解的聲明式事務管理,如果沒有使用Hibernate框架,則將class替換成”org.springframework.jdbc.datasource.DataSourceTransactionManager”

<!-- 配置聲明式事務管理器-->  
    <bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">  
        <property name="sessionFactory" ref="sessionFactory"></property>  
    </bean>  
    <!-- 註解驅動-->  
    <tx:annotation-driven transaction-manager="txManager"/>

3.在要使用事務管理的類或者方法上增加代碼@Transactional,Spring官方團隊建議不要在接口使用。在類上使用@Transactional,類中的所有public方法都將使用事務

    @Transactional
    public class Txtest implements TestService { }

在public方法上使用@Transactional,則該方法使用事務;非public方法使用@Transactional不會報錯,但也不會使用事務,相當於“白做”。

@Transactional
public List<Object> getAll() {
    return null;
}

如果在類上使用@Transactional,但是類中的某個方法不想使用事務,則可以使用

    @Transactional
    public class Txtest implements TestService {   

        @Transactional(propagation = Propagation.NOT_SUPPORTED)
        public List<Object> getAll() {
            return null;
        }   
    }

使用tx標籤配置的攔截器

1.配置sessionFactory

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

2.配置事務管理器

<!-- 事務配置 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

3.配置事務的傳播性

<!-- 使用聲明方式配置事務 -->
    <tx:advice id="TxAdvice" transaction-manager="transactionManager">
         <tx:attributes>
               <tx:method name="query*" read-only="true"/>
               <tx:method name="insert*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
               <tx:method name="update*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
               <tx:method name="delete*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
               <tx:method name="*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
        </tx:attributes>
    </tx:advice>

4.配置參與事務的類

<aop:config>
        <aop:pointcut id="allServiceMethod" expression="execution(* com.chillax.*.service.*.*(..))"/>
        <aop:advisor pointcut-ref="allServiceMethod" advice-ref="TxAdvice" />
    </aop:config>

事務的傳播性

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

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

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

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

這是一個很有用的傳播級別,舉一個應用場景:現在有一個發送100個紅包的操作,在發送之前,要做一些系統的初始化、驗證、數據記錄操作,然後發送100封紅包,然後再記錄發送日誌,發送日誌要求100%的準確,如果日誌不準確,那麼整個父事務邏輯需要回滾。

怎麼處理整個業務需求呢?就是通過這個PROPAGATION_REQUIRES_NEW級別的事務傳播控制就可以完成。發送紅包的子事務不會直接影響到父事務的提交和回滾。

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

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

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

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

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

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

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

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

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

事務的隔離級別

1)@Transactional(isolation = Isolation.SERIALIZABLE):最嚴格的級別,事務串行執行,資源消耗最大;

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

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

4)@Transactional(isolation = Isolation.READ_UNCOMMITTED):保證了讀取過程中不會讀取到非法數據。

1:Dirty reads—讀髒數據。也就是說,比如事務A的未提交(還依然緩存)的數據被事務B讀走,如果事務A失敗回滾,會導致事務B所讀取的的數據是錯誤的。

2:non-repeatable reads—不可重複讀。比如事務A中兩處讀取數據-total-的值。在第一讀的時候,total是100,然後事務B就把total的數據改成200,事務A再讀一次,結果就發現,total竟然就變成200了,造成事務A數據混亂。

3:phantom reads—幻象讀數據。這個和non-repeatable reads相似,也是同一個事務中多次讀不一致的問題。但是non-repeatable reads的不一致是因爲他所要取的數據集被改變了(比如total的數據),但是phantom reads所要讀的數據的不一致卻不是他所要讀的數據集改變,而是他的條件數據集改變。比如Select account.id where account.name=”grace”,第一次讀去了6個符合條件的id,第二次讀取的時候,由於事務b把一個帳號的名字由”dd”改成”grace”,結果取出來了7個數據。

不可重複讀的重點是修改:同樣的條件, 你讀取過的數據, 再次讀取出來發現值不一樣了。
幻讀的重點在於新增或者刪除:同樣的條件, 第1次和第2次讀出來的記錄數不一樣。

而事務的隔離級別會導致讀取到非法數據的情況如下表示:

常用數據庫默認事務隔離級別

MYSQL:默認爲REPEATABLE_READ

SQLSERVER:默認爲READ_COMMITTED

ORACLE:默認爲READ_COMMITTED

@Transactional註解中常用參數說明

readOnly:該屬性用於設置當前事務是否爲只讀事務,設置爲true表示只讀,false則表示可讀寫,默認值爲false。例如:@Transactional(readOnly=true)

rollbackFor:該屬性用於設置需要進行回滾的異常類數組,當方法中拋出指定異常數組中的異常時,則進行事務回滾。例如:

  • 指定單一異常類:@Transactional(rollbackFor=RuntimeException.class);
  • 指定多個異常類:@Transactional(rollbackFor={RuntimeException.class, Exception.class})

rollbackForClassName:該屬性用於設置需要進行回滾的異常類名稱數組,當方法中拋出指定異常名稱數組中的異常時,則進行事務回滾。例如:

  • 指定單一異常類名稱:@Transactional(rollbackForClassName=”RuntimeException”);
  • 指定多個異常類名稱:@Transactional(rollbackForClassName={“RuntimeException”,”Exception”})

noRollbackFor:該屬性用於設置不需要進行回滾的異常類數組,當方法中拋出指定異常數組中的異常時,不進行事務回滾。例如:

  • 指定單一異常類:@Transactional(noRollbackFor=RuntimeException.class);
  • 指定多個異常類:@Transactional(noRollbackFor={RuntimeException.class, Exception.class})

noRollbackForClassName:該屬性用於設置不需要進行回滾的異常類名稱數組,當方法中拋出指定異常名稱數組中的異常時,不進行事務回滾。例如:

  • 指定單一異常類名稱:@Transactional(noRollbackForClassName=”RuntimeException”);
  • 指定多個異常類名:@Transactional(noRollbackForClassName={“RuntimeException”,”Exception”})

propagation:該屬性用於設置事務的傳播行爲,具體取值可參考上文。例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)

isolation:該屬性用於設置底層數據庫的事務隔離級別,事務隔離級別用於處理多事務併發的情況,通常使用數據庫的默認隔離級別即可,基本不需要進行設置

timeout:該屬性用於設置事務的超時秒數,默認值爲-1表示永不超時

注意的幾點:

1)@Transactional只能被應用到public方法上,對於其它非public的方法,如果標記了@Transactional也不會報錯,但方法沒有事務功能.

2)用spring事務管理器,由spring來負責數據庫的打開,提交,回滾。默認遇到運行期例外throw new RuntimeException(“註釋”);會回滾,即遇到不受檢查unchecked的例外時回滾;而遇到需要捕獲的例外throw new Exception(“註釋”);不會回滾,即遇到受檢查的例外就是非運行時拋出的異常,編譯器會檢查到的異常叫受檢查例外或說受檢查異常時,需我們指定方式來讓事務回滾 要想所有異常都回滾,要加上@Transactional( rollbackFor={Exception.class,其它異常})。如果讓unchecked例外不回滾: @Transactional(notRollbackFor=RunTimeException.class)
如下:

@Transactional(rollbackFor=Exception.class) //指定回滾,遇到異常Exception時回滾
    public void methodName() {
    throw new Exception("註釋");

    }
    @Transactional(noRollbackFor=Exception.class)//指定不回滾,遇到運行期例外(throw new RuntimeException("註釋");)會回滾
    public ItimDaoImpl getItemDaoImpl() {
    throw new RuntimeException("註釋");
    }

3)@Transactional註解應該只被應用到public可見度的方法上。如果你在 protected、private或者package-visible的方法上使用@Transactional註解它也不會報錯, 但是這個被註解的方法將不會展示已配置的事務設置。

4)@Transactional註解可以被應用於接口定義和接口方法、類定義和類的public方法上。然而,請注意僅僅@Transactional註解的出現不足於開啓事務行爲,它僅僅 是一種元數據,能夠被可以識別@Transactional註解和上述的配置適當的具有事務行爲的beans所使用。上面的例子中,其實正是 元素的出現 開啓 了事務行爲。

5)Spring團隊的建議是你在具體的類(或類的方法)上使用@Transactional註解,而不要使用在類所要實現的任何接口上。你當然可以在接口上使用@Transactional註解,但是這將只能當你設置了基於接口的代理時它才生效。因爲註解是不能繼承的,這就意味着如果你正在使用基於類的代理時,那麼事務的設置將不能被基於類的代理所識別,而且對象也將不會被事務代理所包裝(將被確認爲嚴重的)。因此,請接受Spring團隊的建議並且在具體的類上使用@Transactional註解。

聲明:本文章裝載至Spring事務配置及事務的傳播性與隔離級別詳解

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