2018-12-03 關(guān)于事務(wù)管理的理解和Spring事務(wù)管理詳解

1 初步理解

理解事務(wù)之前,先講一個你日常生活中最常干的事:取錢。
比如你去ATM機取1000塊錢,大體有兩個步驟:首先輸入密碼金額,銀行卡扣掉1000元錢;然后ATM出1000元錢。這兩個步驟必須是要么都執(zhí)行要么都不執(zhí)行。如果銀行卡扣除了1000塊但是ATM出錢失敗的話,你將會損失1000元;如果銀行卡扣錢失敗但是ATM卻出了1000塊,那么銀行將損失1000元。所以,如果一個步驟成功另一個步驟失敗對雙方都不是好事,如果不管哪一個步驟失敗了以后,整個取錢過程都能回滾,也就是完全取消所有操作的話,這對雙方都是極好的。
事務(wù)就是用來解決類似問題的。事務(wù)是一系列的動作,它們綜合在一起才是一個完整的工作單元,這些動作必須全部完成,如果有一個失敗的話,那么事務(wù)就會回滾到最開始的狀態(tài),仿佛什么都沒發(fā)生過一樣。
在企業(yè)級應(yīng)用程序開發(fā)中,事務(wù)管理必不可少的技術(shù),用來確保數(shù)據(jù)的完整性和一致性。
事務(wù)有四個特性:ACID

  • 原子性(Atomicity):事務(wù)是一個原子操作,由一系列動作組成。事務(wù)的原子性確保動作要么全部完成,要么完全不起作用。
  • 一致性(Consistency):一旦事務(wù)完成(不管成功還是失?。?,系統(tǒng)必須確保它所建模的業(yè)務(wù)處于一致的狀態(tài),而不會是部分完成部分失敗。在現(xiàn)實中的數(shù)據(jù)不應(yīng)該被破壞。
  • 隔離性(Isolation):可能有許多事務(wù)會同時處理相同的數(shù)據(jù),因此每個事務(wù)都應(yīng)該與其他事務(wù)隔離開來,防止數(shù)據(jù)損壞。
  • 持久性(Durability):一旦事務(wù)完成,無論發(fā)生什么系統(tǒng)錯誤,它的結(jié)果都不應(yīng)該受到影響,這樣就能從任何系統(tǒng)崩潰中恢復(fù)過來。通常情況下,事務(wù)的結(jié)果被寫到持久化存儲器中。

2 核心接口

Spring事務(wù)管理的實現(xiàn)有許多細節(jié),如果對整個接口框架有個大體了解會非常有利于我們理解事務(wù),下面通過講解Spring的事務(wù)接口來了解Spring實現(xiàn)事務(wù)的具體策略。
Spring事務(wù)管理涉及的接口的聯(lián)系如下:

技術(shù)分享

2.1 事務(wù)管理器

Spring并不直接管理事務(wù),而是提供了多種事務(wù)管理器,他們將事務(wù)管理的職責委托給Hibernate或者JTA等持久化機制所提供的相關(guān)平臺框架的事務(wù)來實現(xiàn)。
Spring事務(wù)管理器的接口是org.springframework.transaction.PlatformTransactionManager,通過這個接口,Spring為各個平臺如JDBC、Hibernate等都提供了對應(yīng)的事務(wù)管理器,但是具體的實現(xiàn)就是各個平臺自己的事情了。此接口的內(nèi)容如下:

Public interface PlatformTransactionManager()...{      // 由TransactionDefinition得到TransactionStatus對象    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;     // 提交    Void commit(TransactionStatus status) throws TransactionException;      // 回滾    Void rollback(TransactionStatus status) throws TransactionException;      } 

從這里可知具體的具體的事務(wù)管理機制對Spring來說是透明的,它并不關(guān)心那些,那些是對應(yīng)各個平臺需要關(guān)心的,所以Spring事務(wù)管理的一個優(yōu)點就是為不同的事務(wù)API提供一致的編程模型,如JTA、JDBC、Hibernate、JPA。下面分別介紹各個平臺框架實現(xiàn)事務(wù)管理的機制。

2.1.1 JDBC事務(wù)

如果應(yīng)用程序中直接使用JDBC來進行持久化,DataSourceTransactionManager會為你處理事務(wù)邊界。為了使用DataSourceTransactionManager,你需要使用如下的XML將其裝配到應(yīng)用程序的上下文定義中:

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">        <property name="dataSource" ref="dataSource" />    </bean>

實際上,DataSourceTransactionManager是通過調(diào)用java.sql.Connection來管理事務(wù),而后者是通過DataSource獲取到的。通過調(diào)用連接的commit()方法來提交事務(wù),同樣,事務(wù)失敗則通過調(diào)用rollback()方法進行回滾。

2.1.2 Hibernate事務(wù)

如果應(yīng)用程序的持久化是通過Hibernate實習的,那么你需要使用HibernateTransactionManager。對于Hibernate3,需要在Spring上下文定義中添加如下的<bean>聲明:

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

sessionFactory屬性需要裝配一個Hibernate的session工廠,HibernateTransactionManager的實現(xiàn)細節(jié)是它將事務(wù)管理的職責委托給org.hibernate.Transaction對象,而后者是從Hibernate Session中獲取到的。當事務(wù)成功完成時,HibernateTransactionManager將會調(diào)用Transaction對象的commit()方法,反之,將會調(diào)用rollback()方法。

2.1.3 Java持久化API事務(wù)(JPA)

Hibernate多年來一直是事實上的Java持久化標準,但是現(xiàn)在Java持久化API作為真正的Java持久化標準進入大家的視野。如果你計劃使用JPA的話,那你需要使用Spring的JpaTransactionManager來處理事務(wù)。你需要在Spring中這樣配置JpaTransactionManager:

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

JpaTransactionManager只需要裝配一個JPA實體管理工廠(javax.persistence.EntityManagerFactory接口的任意實現(xiàn))。JpaTransactionManager將與由工廠所產(chǎn)生的JPA EntityManager合作來構(gòu)建事務(wù)。

2.1.4 Java原生API事務(wù)

如果你沒有使用以上所述的事務(wù)管理,或者是跨越了多個事務(wù)管理源(比如兩個或者是多個不同的數(shù)據(jù)源),你就需要使用JtaTransactionManager:

    <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">        <property name="transactionManagerName" value="java:/TransactionManager" />    </bean>

JtaTransactionManager將事務(wù)管理的責任委托給javax.transaction.UserTransaction和javax.transaction.TransactionManager對象,其中事務(wù)成功完成通過UserTransaction.commit()方法提交,事務(wù)失敗通過UserTransaction.rollback()方法回滾。

2.2 基本事務(wù)屬性的定義

上面講到的事務(wù)管理器接口PlatformTransactionManager通過getTransaction(TransactionDefinition definition)方法來得到事務(wù),這個方法里面的參數(shù)是TransactionDefinition類,這個類就定義了一些基本的事務(wù)屬性。
那么什么是事務(wù)屬性呢?事務(wù)屬性可以理解成事務(wù)的一些基本配置,描述了事務(wù)策略如何應(yīng)用到方法上。事務(wù)屬性包含了5個方面,如圖所示:

技術(shù)分享

而TransactionDefinition接口內(nèi)容如下:

public interface TransactionDefinition {    int getPropagationBehavior(); // 返回事務(wù)的傳播行為    int getIsolationLevel(); // 返回事務(wù)的隔離級別,事務(wù)管理器根據(jù)它來控制另外一個事務(wù)可以看到本事務(wù)內(nèi)的哪些數(shù)據(jù)    int getTimeout();  // 返回事務(wù)必須在多少秒內(nèi)完成    boolean isReadOnly(); // 事務(wù)是否只讀,事務(wù)管理器能夠根據(jù)這個返回值進行優(yōu)化,確保事務(wù)是只讀的} 

我們可以發(fā)現(xiàn)TransactionDefinition正好用來定義事務(wù)屬性,下面詳細介紹一下各個事務(wù)屬性。

2.2.1 傳播行為

事務(wù)的第一個方面是傳播行為(propagation behavior)。當事務(wù)方法被另一個事務(wù)方法調(diào)用時,必須指定事務(wù)應(yīng)該如何傳播。例如:方法可能繼續(xù)在現(xiàn)有事務(wù)中運行,也可能開啟一個新事務(wù),并在自己的事務(wù)中運行。Spring定義了七種傳播行為:

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

注:以下具體講解傳播行為的內(nèi)容參考自Spring事務(wù)機制詳解
(1)PROPAGATION_REQUIRED 如果存在一個事務(wù),則支持當前事務(wù)。如果沒有事務(wù)則開啟一個新的事務(wù)。

//事務(wù)屬性 PROPAGATION_REQUIREDmethodA{    ……    methodB();    ……}
//事務(wù)屬性 PROPAGATION_REQUIREDmethodB{   ……}

使用spring聲明式事務(wù),spring使用AOP來支持聲明式事務(wù),會根據(jù)事務(wù)屬性,自動在方法調(diào)用之前決定是否開啟一個事務(wù),并在方法執(zhí)行之后決定事務(wù)提交或回滾事務(wù)。

單獨調(diào)用methodB方法:

main{     metodB(); }  

相當于

Main{     Connection con=null;     try{         con = getConnection();         con.setAutoCommit(false);          //方法調(diào)用        methodB();          //提交事務(wù)        con.commit();     } Catch(RuntimeException ex) {         //回滾事務(wù)        con.rollback();       } finally {         //釋放資源        closeCon();     } } 

Spring保證在methodB方法中所有的調(diào)用都獲得到一個相同的連接。在調(diào)用methodB時,沒有一個存在的事務(wù),所以獲得一個新的連接,開啟了一個新的事務(wù)。
單獨調(diào)用MethodA時,在MethodA內(nèi)又會調(diào)用MethodB.

執(zhí)行效果相當于:

main{     Connection con = null;     try{         con = getConnection();         methodA();         con.commit();     } catch(RuntimeException ex) {         con.rollback();     } finally {            closeCon();     }  } 

調(diào)用MethodA時,環(huán)境中沒有事務(wù),所以開啟一個新的事務(wù).當在MethodA中調(diào)用MethodB時,環(huán)境中已經(jīng)有了一個事務(wù),所以methodB就加入當前事務(wù)。

(2)PROPAGATION_SUPPORTS 如果存在一個事務(wù),支持當前事務(wù)。如果沒有事務(wù),則非事務(wù)的執(zhí)行。但是對于事務(wù)同步的事務(wù)管理器,PROPAGATION_SUPPORTS與不使用事務(wù)有少許不同。

//事務(wù)屬性 PROPAGATION_REQUIREDmethodA(){  methodB();} //事務(wù)屬性 PROPAGATION_SUPPORTSmethodB(){  ……}

單純的調(diào)用methodB時,methodB方法是非事務(wù)的執(zhí)行的。當調(diào)用methdA時,methodB則加入了methodA的事務(wù)中,事務(wù)地執(zhí)行。

(3)PROPAGATION_MANDATORY 如果已經(jīng)存在一個事務(wù),支持當前事務(wù)。如果沒有一個活動的事務(wù),則拋出異常。

//事務(wù)屬性 PROPAGATION_REQUIREDmethodA(){    methodB();} //事務(wù)屬性 PROPAGATION_MANDATORY    methodB(){    ……}

當單獨調(diào)用methodB時,因為當前沒有一個活動的事務(wù),則會拋出異常throw new IllegalTransactionStateException(“Transaction propagation ‘mandatory’ but no existing transaction found”);當調(diào)用methodA時,methodB則加入到methodA的事務(wù)中,事務(wù)地執(zhí)行。

(4)PROPAGATION_REQUIRES_NEW 總是開啟一個新的事務(wù)。如果一個事務(wù)已經(jīng)存在,則將這個存在的事務(wù)掛起。

//事務(wù)屬性 PROPAGATION_REQUIREDmethodA(){    doSomeThingA();    methodB();    doSomeThingB();} //事務(wù)屬性 PROPAGATION_REQUIRES_NEWmethodB(){    ……}

調(diào)用A方法:

main(){    methodA();}

相當于

main(){    TransactionManager tm = null;    try{        //獲得一個JTA事務(wù)管理器        tm = getTransactionManager();        tm.begin();//開啟一個新的事務(wù)        Transaction ts1 = tm.getTransaction();        doSomeThing();        tm.suspend();//掛起當前事務(wù)        try{            tm.begin();//重新開啟第二個事務(wù)            Transaction ts2 = tm.getTransaction();            methodB();            ts2.commit();//提交第二個事務(wù)        } Catch(RunTimeException ex) {            ts2.rollback();//回滾第二個事務(wù)        } finally {            //釋放資源        }        //methodB執(zhí)行完后,恢復(fù)第一個事務(wù)        tm.resume(ts1);        doSomeThingB();        ts1.commit();//提交第一個事務(wù)    } catch(RunTimeException ex) {        ts1.rollback();//回滾第一個事務(wù)    } finally {        //釋放資源    }}

在這里,我把ts1稱為外層事務(wù),ts2稱為內(nèi)層事務(wù)。從上面的代碼可以看出,ts2與ts1是兩個獨立的事務(wù),互不相干。Ts2是否成功并不依賴于 ts1。如果methodA方法在調(diào)用methodB方法后的doSomeThingB方法失敗了,而methodB方法所做的結(jié)果依然被提交。而除了 methodB之外的其它代碼導致的結(jié)果卻被回滾了。使用PROPAGATION_REQUIRES_NEW,需要使用 JtaTransactionManager作為事務(wù)管理器。

(5)PROPAGATION_NOT_SUPPORTED 總是非事務(wù)地執(zhí)行,并掛起任何存在的事務(wù)。使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作為事務(wù)管理器。(代碼示例同上,可同理推出)

(6)PROPAGATION_NEVER 總是非事務(wù)地執(zhí)行,如果存在一個活動事務(wù),則拋出異常。

(7)PROPAGATION_NESTED如果一個活動的事務(wù)存在,則運行在一個嵌套的事務(wù)中. 如果沒有活動事務(wù), 則按TransactionDefinition.PROPAGATION_REQUIRED 屬性執(zhí)行。這是一個嵌套事務(wù),使用JDBC 3.0驅(qū)動時,僅僅支持DataSourceTransactionManager作為事務(wù)管理器。需要JDBC 驅(qū)動的java.sql.Savepoint類。有一些JTA的事務(wù)管理器實現(xiàn)可能也提供了同樣的功能。使用PROPAGATION_NESTED,還需要把PlatformTransactionManager的nestedTransactionAllowed屬性設(shè)為true;而 nestedTransactionAllowed屬性值默認為false。

//事務(wù)屬性 PROPAGATION_REQUIREDmethodA(){    doSomeThingA();    methodB();    doSomeThingB();} //事務(wù)屬性 PROPAGATION_NESTEDmethodB(){    ……}

如果單獨調(diào)用methodB方法,則按REQUIRED屬性執(zhí)行。如果調(diào)用methodA方法,相當于下面的效果:

main(){    Connection con = null;    Savepoint savepoint = null;    try{        con = getConnection();        con.setAutoCommit(false);        doSomeThingA();        savepoint = con2.setSavepoint();        try{            methodB();        } catch(RuntimeException ex) {            con.rollback(savepoint);        } finally {            //釋放資源        }        doSomeThingB();        con.commit();    } catch(RuntimeException ex) {        con.rollback();    } finally {        //釋放資源    }}

當methodB方法調(diào)用之前,調(diào)用setSavepoint方法,保存當前的狀態(tài)到savepoint。如果methodB方法調(diào)用失敗,則恢復(fù)到之前保存的狀態(tài)。但是需要注意的是,這時的事務(wù)并沒有進行提交,如果后續(xù)的代碼(doSomeThingB()方法)調(diào)用失敗,則回滾包括methodB方法的所有操作。

嵌套事務(wù)一個非常重要的概念就是內(nèi)層事務(wù)依賴于外層事務(wù)。外層事務(wù)失敗時,會回滾內(nèi)層事務(wù)所做的動作。而內(nèi)層事務(wù)操作失敗并不會引起外層事務(wù)的回滾。

PROPAGATION_NESTED 與PROPAGATION_REQUIRES_NEW的區(qū)別:它們非常類似,都像一個嵌套事務(wù),如果不存在一個活動的事務(wù),都會開啟一個新的事務(wù)。使用 PROPAGATION_REQUIRES_NEW時,內(nèi)層事務(wù)與外層事務(wù)就像兩個獨立的事務(wù)一樣,一旦內(nèi)層事務(wù)進行了提交后,外層事務(wù)不能對其進行回滾。兩個事務(wù)互不影響。兩個事務(wù)不是一個真正的嵌套事務(wù)。同時它需要JTA事務(wù)管理器的支持。

使用PROPAGATION_NESTED時,外層事務(wù)的回滾可以引起內(nèi)層事務(wù)的回滾。而內(nèi)層事務(wù)的異常并不會導致外層事務(wù)的回滾,它是一個真正的嵌套事務(wù)。DataSourceTransactionManager使用savepoint支持PROPAGATION_NESTED時,需要JDBC 3.0以上驅(qū)動及1.4以上的JDK版本支持。其它的JTA TrasactionManager實現(xiàn)可能有不同的支持方式。

PROPAGATION_REQUIRES_NEW 啟動一個新的, 不依賴于環(huán)境的 “內(nèi)部” 事務(wù). 這個事務(wù)將被完全 commited 或 rolled back 而不依賴于外部事務(wù), 它擁有自己的隔離范圍, 自己的鎖, 等等. 當內(nèi)部事務(wù)開始執(zhí)行時, 外部事務(wù)將被掛起, 內(nèi)務(wù)事務(wù)結(jié)束時, 外部事務(wù)將繼續(xù)執(zhí)行。

另一方面, PROPAGATION_NESTED 開始一個 “嵌套的” 事務(wù), 它是已經(jīng)存在事務(wù)的一個真正的子事務(wù). 潛套事務(wù)開始執(zhí)行時, 它將取得一個 savepoint. 如果這個嵌套事務(wù)失敗, 我們將回滾到此 savepoint. 潛套事務(wù)是外部事務(wù)的一部分, 只有外部事務(wù)結(jié)束后它才會被提交。

由此可見, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大區(qū)別在于, PROPAGATION_REQUIRES_NEW 完全是一個新的事務(wù), 而 PROPAGATION_NESTED 則是外部事務(wù)的子事務(wù), 如果外部事務(wù) commit, 嵌套事務(wù)也會被 commit, 這個規(guī)則同樣適用于 roll back.

PROPAGATION_REQUIRED應(yīng)該是我們首先的事務(wù)傳播行為。它能夠滿足我們大多數(shù)的事務(wù)需求。

2.2.2 隔離級別

事務(wù)的第二個維度就是隔離級別(isolation level)。隔離級別定義了一個事務(wù)可能受其他并發(fā)事務(wù)影響的程度。
(1)并發(fā)事務(wù)引起的問題
在典型的應(yīng)用程序中,多個事務(wù)并發(fā)運行,經(jīng)常會操作相同的數(shù)據(jù)來完成各自的任務(wù)。并發(fā)雖然是必須的,但可能會導致一下的問題。

  • 臟讀(Dirty reads)——臟讀發(fā)生在一個事務(wù)讀取了另一個事務(wù)改寫但尚未提交的數(shù)據(jù)時。如果改寫在稍后被回滾了,那么第一個事務(wù)獲取的數(shù)據(jù)就是無效的。
  • 不可重復(fù)讀(Nonrepeatable read)——不可重復(fù)讀發(fā)生在一個事務(wù)執(zhí)行相同的查詢兩次或兩次以上,但是每次都得到不同的數(shù)據(jù)時。這通常是因為另一個并發(fā)事務(wù)在兩次查詢期間進行了更新。
  • 幻讀(Phantom read)——幻讀與不可重復(fù)讀類似。它發(fā)生在一個事務(wù)(T1)讀取了幾行數(shù)據(jù),接著另一個并發(fā)事務(wù)(T2)插入了一些數(shù)據(jù)時。在隨后的查詢中,第一個事務(wù)(T1)就會發(fā)現(xiàn)多了一些原本不存在的記錄。

不可重復(fù)讀與幻讀的區(qū)別

不可重復(fù)讀的重點是修改:
同樣的條件, 你讀取過的數(shù)據(jù), 再次讀取出來發(fā)現(xiàn)值不一樣了
例如:在事務(wù)1中,Mary 讀取了自己的工資為1000,操作并沒有完成

    con1 = getConnection();      select salary from employee empId ="Mary";  

在事務(wù)2中,這時財務(wù)人員修改了Mary的工資為2000,并提交了事務(wù).

    con2 = getConnection();      update employee set salary = 2000;      con2.commit();  

在事務(wù)1中,Mary 再次讀取自己的工資時,工資變?yōu)榱?000

    //con1      select salary from employee empId ="Mary"; 

在一個事務(wù)中前后兩次讀取的結(jié)果并不一致,導致了不可重復(fù)讀。

幻讀的重點在于新增或者刪除:
同樣的條件, 第1次和第2次讀出來的記錄數(shù)不一樣
例如:目前工資為1000的員工有10人。事務(wù)1,讀取所有工資為1000的員工。

    con1 = getConnection();      Select * from employee where salary =1000; 

共讀取10條記錄

這時另一個事務(wù)向employee表插入了一條員工記錄,工資也為1000

    con2 = getConnection();      Insert into employee(empId,salary) values("Lili",1000);      con2.commit();  

事務(wù)1再次讀取所有工資為1000的員工

    //con1      select * from employee where salary =1000;  

共讀取到了11條記錄,這就產(chǎn)生了幻像讀。

從總的結(jié)果來看, 似乎不可重復(fù)讀和幻讀都表現(xiàn)為兩次讀取的結(jié)果不一致。但如果你從控制的角度來看, 兩者的區(qū)別就比較大。
對于前者, 只需要鎖住滿足條件的記錄。
對于后者, 要鎖住滿足條件及其相近的記錄。

(2)隔離級別

隔離級別 含義
ISOLATION_DEFAULT 使用后端數(shù)據(jù)庫默認的隔離級別
ISOLATION_READ_UNCOMMITTED 最低的隔離級別,允許讀取尚未提交的數(shù)據(jù)變更,可能會導致臟讀、幻讀或不可重復(fù)讀
ISOLATION_READ_COMMITTED 允許讀取并發(fā)事務(wù)已經(jīng)提交的數(shù)據(jù),可以阻止臟讀,但是幻讀或不可重復(fù)讀仍有可能發(fā)生
ISOLATION_REPEATABLE_READ 對同一字段的多次讀取結(jié)果都是一致的,除非數(shù)據(jù)是被本身事務(wù)自己所修改,可以阻止臟讀和不可重復(fù)讀,但幻讀仍有可能發(fā)生
ISOLATION_SERIALIZABLE 最高的隔離級別,完全服從ACID的隔離級別,確保阻止臟讀、不可重復(fù)讀以及幻讀,也是最慢的事務(wù)隔離級別,因為它通常是通過完全鎖定事務(wù)相關(guān)的數(shù)據(jù)庫表來實現(xiàn)的

2.2.3 只讀

事務(wù)的第三個特性是它是否為只讀事務(wù)。如果事務(wù)只對后端的數(shù)據(jù)庫進行該操作,數(shù)據(jù)庫可以利用事務(wù)的只讀特性來進行一些特定的優(yōu)化。通過將事務(wù)設(shè)置為只讀,你就可以給數(shù)據(jù)庫一個機會,讓它應(yīng)用它認為合適的優(yōu)化措施。

2.2.4 事務(wù)超時

為了使應(yīng)用程序很好地運行,事務(wù)不能運行太長的時間。因為事務(wù)可能涉及對后端數(shù)據(jù)庫的鎖定,所以長時間的事務(wù)會不必要的占用數(shù)據(jù)庫資源。事務(wù)超時就是事務(wù)的一個定時器,在特定時間內(nèi)事務(wù)如果沒有執(zhí)行完畢,那么就會自動回滾,而不是一直等待其結(jié)束。

2.2.5 回滾規(guī)則

事務(wù)五邊形的最后一個方面是一組規(guī)則,這些規(guī)則定義了哪些異常會導致事務(wù)回滾而哪些不會。默認情況下,事務(wù)只有遇到運行期異常時才會回滾,而在遇到檢查型異常時不會回滾(這一行為與EJB的回滾行為是一致的)
但是你可以聲明事務(wù)在遇到特定的檢查型異常時像遇到運行期異常那樣回滾。同樣,你還可以聲明事務(wù)遇到特定的異常不回滾,即使這些異常是運行期異常。

2.3 事務(wù)狀態(tài)

上面講到的調(diào)用PlatformTransactionManager接口的getTransaction()的方法得到的是TransactionStatus接口的一個實現(xiàn),這個接口的內(nèi)容如下:

public interface TransactionStatus{    boolean isNewTransaction(); // 是否是新的事物    boolean hasSavepoint(); // 是否有恢復(fù)點    void setRollbackOnly();  // 設(shè)置為只回滾    boolean isRollbackOnly(); // 是否為只回滾    boolean isCompleted; // 是否已完成} 

可以發(fā)現(xiàn)這個接口描述的是一些處理事務(wù)提供簡單的控制事務(wù)執(zhí)行和查詢事務(wù)狀態(tài)的方法,在回滾或提交的時候需要應(yīng)用對應(yīng)的事務(wù)狀態(tài)。

3 編程式事務(wù)

3.1 編程式和聲明式事務(wù)的區(qū)別

Spring提供了對編程式事務(wù)和聲明式事務(wù)的支持,編程式事務(wù)允許用戶在代碼中精確定義事務(wù)的邊界,而聲明式事務(wù)(基于AOP)有助于用戶將操作與事務(wù)規(guī)則進行解耦。
簡單地說,編程式事務(wù)侵入到了業(yè)務(wù)代碼里面,但是提供了更加詳細的事務(wù)管理;而聲明式事務(wù)由于基于AOP,所以既能起到事務(wù)管理的作用,又可以不影響業(yè)務(wù)代碼的具體實現(xiàn)。

3.2 如何實現(xiàn)編程式事務(wù)?

Spring提供兩種方式的編程式事務(wù)管理,分別是:使用TransactionTemplate和直接使用PlatformTransactionManager。

3.2.1 使用TransactionTemplate

采用TransactionTemplate和采用其他Spring模板,如JdbcTempalte和HibernateTemplate是一樣的方法。它使用回調(diào)方法,把應(yīng)用程序從處理取得和釋放資源中解脫出來。如同其他模板,TransactionTemplate是線程安全的。代碼片段:

    TransactionTemplate tt = new TransactionTemplate(); // 新建一個TransactionTemplate    Object result = tt.execute(        new TransactionCallback(){              public Object doTransaction(TransactionStatus status){                  updateOperation();                  return resultOfUpdateOperation();              }      }); // 執(zhí)行execute方法進行事務(wù)管理

使用TransactionCallback()可以返回一個值。如果使用TransactionCallbackWithoutResult則沒有返回值。

3.2.2 使用PlatformTransactionManager

示例代碼如下:

    DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); //定義一個某個框架平臺的TransactionManager,如JDBC、Hibernate    dataSourceTransactionManager.setDataSource(this.getJdbcTemplate().getDataSource()); // 設(shè)置數(shù)據(jù)源    DefaultTransactionDefinition transDef = new DefaultTransactionDefinition(); // 定義事務(wù)屬性    transDef.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED); // 設(shè)置傳播行為屬性    TransactionStatus status = dataSourceTransactionManager.getTransaction(transDef); // 獲得事務(wù)狀態(tài)    try {        // 數(shù)據(jù)庫操作        dataSourceTransactionManager.commit(status);// 提交    } catch (Exception e) {        dataSourceTransactionManager.rollback(status);// 回滾    }

4 聲明式事務(wù)

4.1 配置方式

注:以下配置代碼參考自Spring事務(wù)配置的五種方式

根據(jù)代理機制的不同,總結(jié)了五種Spring事務(wù)的配置方式,配置文件如下:

(1)每個Bean都有一個代理

<?xml version="1.0" encoding="UTF-8"?><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"    xsi:schemaLocation="http://www.springframework.org/schema/beans           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd           http://www.springframework.org/schema/context           http://www.springframework.org/schema/context/spring-context-2.5.xsd           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">     <bean id="sessionFactory"             class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">         <property name="configLocation" value="classpath:hibernate.cfg.xml" />         <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />    </bean>      <!-- 定義事務(wù)管理器(聲明式的事務(wù)) -->     <bean id="transactionManager"        class="org.springframework.orm.hibernate3.HibernateTransactionManager">        <property name="sessionFactory" ref="sessionFactory" />    </bean>     <!-- 配置DAO -->    <bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">        <property name="sessionFactory" ref="sessionFactory" />    </bean>     <bean id="userDao"         class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">            <!-- 配置事務(wù)管理器 -->            <property name="transactionManager" ref="transactionManager" />            <property name="target" ref="userDaoTarget" />          <property name="proxyInterfaces" value="com.bluesky.spring.dao.GeneratorDao" />        <!-- 配置事務(wù)屬性 -->         <property name="transactionAttributes">             <props>                 <prop key="*">PROPAGATION_REQUIRED</prop>            </props>         </property>     </bean> </beans>

(2)所有Bean共享一個代理基類

<?xml version="1.0" encoding="UTF-8"?><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"    xsi:schemaLocation="http://www.springframework.org/schema/beans           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd           http://www.springframework.org/schema/context           http://www.springframework.org/schema/context/spring-context-2.5.xsd           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">     <bean id="sessionFactory"             class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">         <property name="configLocation" value="classpath:hibernate.cfg.xml" />         <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />    </bean>      <!-- 定義事務(wù)管理器(聲明式的事務(wù)) -->     <bean id="transactionManager"        class="org.springframework.orm.hibernate3.HibernateTransactionManager">        <property name="sessionFactory" ref="sessionFactory" />    </bean>     <bean id="transactionBase"             class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"             lazy-init="true" abstract="true">         <!-- 配置事務(wù)管理器 -->         <property name="transactionManager" ref="transactionManager" />         <!-- 配置事務(wù)屬性 -->         <property name="transactionAttributes">             <props>                 <prop key="*">PROPAGATION_REQUIRED</prop>             </props>         </property>     </bean>        <!-- 配置DAO -->    <bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">        <property name="sessionFactory" ref="sessionFactory" />    </bean>     <bean id="userDao" parent="transactionBase" >         <property name="target" ref="userDaoTarget" />      </bean></beans>

(3)使用攔截器

<?xml version="1.0" encoding="UTF-8"?><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"    xsi:schemaLocation="http://www.springframework.org/schema/beans           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd           http://www.springframework.org/schema/context           http://www.springframework.org/schema/context/spring-context-2.5.xsd           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">     <bean id="sessionFactory"             class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">         <property name="configLocation" value="classpath:hibernate.cfg.xml" />         <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />    </bean>      <!-- 定義事務(wù)管理器(聲明式的事務(wù)) -->     <bean id="transactionManager"        class="org.springframework.orm.hibernate3.HibernateTransactionManager">        <property name="sessionFactory" ref="sessionFactory" />    </bean>      <bean id="transactionInterceptor"         class="org.springframework.transaction.interceptor.TransactionInterceptor">         <property name="transactionManager" ref="transactionManager" />         <!-- 配置事務(wù)屬性 -->         <property name="transactionAttributes">             <props>                 <prop key="*">PROPAGATION_REQUIRED</prop>             </props>         </property>     </bean>     <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">         <property name="beanNames">             <list>                 <value>*Dao</value>            </list>         </property>         <property name="interceptorNames">             <list>                 <value>transactionInterceptor</value>             </list>         </property>     </bean>      <!-- 配置DAO -->    <bean id="userDao" class="com.bluesky.spring.dao.UserDaoImpl">        <property name="sessionFactory" ref="sessionFactory" />    </bean></beans>

(4)使用tx標簽配置的攔截器

<?xml version="1.0" encoding="UTF-8"?><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-2.5.xsd           http://www.springframework.org/schema/context           http://www.springframework.org/schema/context/spring-context-2.5.xsd           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">     <context:annotation-config />    <context:component-scan base-package="com.bluesky" />     <bean id="sessionFactory"             class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">         <property name="configLocation" value="classpath:hibernate.cfg.xml" />         <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />    </bean>      <!-- 定義事務(wù)管理器(聲明式的事務(wù)) -->     <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="*" propagation="REQUIRED" />        </tx:attributes>    </tx:advice>     <aop:config>        <aop:pointcut id="interceptorPointCuts"            expression="execution(* com.bluesky.spring.dao.*.*(..))" />        <aop:advisor advice-ref="txAdvice"            pointcut-ref="interceptorPointCuts" />           </aop:config>     </beans>

(5)全注解

<?xml version="1.0" encoding="UTF-8"?><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-2.5.xsd           http://www.springframework.org/schema/context           http://www.springframework.org/schema/context/spring-context-2.5.xsd           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">     <context:annotation-config />    <context:component-scan base-package="com.bluesky" />     <tx:annotation-driven transaction-manager="transactionManager"/>     <bean id="sessionFactory"             class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">         <property name="configLocation" value="classpath:hibernate.cfg.xml" />         <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />    </bean>      <!-- 定義事務(wù)管理器(聲明式的事務(wù)) -->     <bean id="transactionManager"        class="org.springframework.orm.hibernate3.HibernateTransactionManager">        <property name="sessionFactory" ref="sessionFactory" />    </bean> </beans>

此時在DAO上需加上@Transactional注解,如下:

package com.bluesky.spring.dao; import java.util.List; import org.hibernate.SessionFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.orm.hibernate3.support.HibernateDaoSupport;import org.springframework.stereotype.Component; import com.bluesky.spring.domain.User; @Transactional@Component("userDao")public class UserDaoImpl extends HibernateDaoSupport implements UserDao {     public List<User> listUsers() {        return this.getSession().createQuery("from User").list();    }  }

4.2 一個聲明式事務(wù)的實例

注:該實例參考自Spring中的事務(wù)管理實例詳解

首先是數(shù)據(jù)庫表
book(isbn, book_name, price)
account(username, balance)
book_stock(isbn, stock)

然后是XML配置

<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/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.0.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">     <import resource="applicationContext-db.xml" />     <context:component-scan        base-package="com.springinaction.transaction">    </context:component-scan>     <tx:annotation-driven transaction-manager="txManager"/>     <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">        <property name="dataSource" ref="dataSource" />    </bean> </beans>

使用的類
BookShopDao

package com.springinaction.transaction; public interface BookShopDao {    // 根據(jù)書號獲取書的單價    public int findBookPriceByIsbn(String isbn);    // 更新書的庫存,使書號對應(yīng)的庫存-1    public void updateBookStock(String isbn);    // 更新用戶的賬戶余額:account的balance-price    public void updateUserAccount(String username, int price);}

BookShopDaoImpl

package com.springinaction.transaction; import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.stereotype.Repository; @Repository("bookShopDao")public class BookShopDaoImpl implements BookShopDao {     @Autowired    private JdbcTemplate JdbcTemplate;     @Override    public int findBookPriceByIsbn(String isbn) {        String sql = "SELECT price FROM book WHERE isbn = ?";         return JdbcTemplate.queryForObject(sql, Integer.class, isbn);    }     @Override    public void updateBookStock(String isbn) {        //檢查書的庫存是否足夠,若不夠,則拋出異常        String sql2 = "SELECT stock FROM book_stock WHERE isbn = ?";        int stock = JdbcTemplate.queryForObject(sql2, Integer.class, isbn);        if (stock == 0) {            throw new BookStockException("庫存不足!");        }        String sql = "UPDATE book_stock SET stock = stock - 1 WHERE isbn = ?";        JdbcTemplate.update(sql, isbn);    }     @Override    public void updateUserAccount(String username, int price) {        //檢查余額是否不足,若不足,則拋出異常        String sql2 = "SELECT balance FROM account WHERE username = ?";        int balance = JdbcTemplate.queryForObject(sql2, Integer.class, username);        if (balance < price) {            throw new UserAccountException("余額不足!");        }               String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";        JdbcTemplate.update(sql, price, username);    } }

BookShopService

package com.springinaction.transaction;public interface BookShopService {     public void purchase(String username, String isbn);}

BookShopServiceImpl

package com.springinaction.transaction; import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Isolation;import org.springframework.transaction.annotation.Propagation;import org.springframework.transaction.annotation.Transactional; @Service("bookShopService")public class BookShopServiceImpl implements BookShopService {     @Autowired    private BookShopDao bookShopDao;     /**     * 1.添加事務(wù)注解     * 使用propagation 指定事務(wù)的傳播行為,即當前的事務(wù)方法被另外一個事務(wù)方法調(diào)用時如何使用事務(wù)。     * 默認取值為REQUIRED,即使用調(diào)用方法的事務(wù)     * REQUIRES_NEW:使用自己的事務(wù),調(diào)用的事務(wù)方法的事務(wù)被掛起。     *     * 2.使用isolation 指定事務(wù)的隔離級別,最常用的取值為READ_COMMITTED     * 3.默認情況下 Spring 的聲明式事務(wù)對所有的運行時異常進行回滾,也可以通過對應(yīng)的屬性進行設(shè)置。通常情況下,默認值即可。     * 4.使用readOnly 指定事務(wù)是否為只讀。 表示這個事務(wù)只讀取數(shù)據(jù)但不更新數(shù)據(jù),這樣可以幫助數(shù)據(jù)庫引擎優(yōu)化事務(wù)。若真的是一個只讀取數(shù)據(jù)庫值得方法,應(yīng)設(shè)置readOnly=true     * 5.使用timeOut 指定強制回滾之前事務(wù)可以占用的時間。     */    @Transactional(propagation=Propagation.REQUIRES_NEW,            isolation=Isolation.READ_COMMITTED,            noRollbackFor={UserAccountException.class},            readOnly=true, timeout=3)    @Override    public void purchase(String username, String isbn) {        //1.獲取書的單價        int price = bookShopDao.findBookPriceByIsbn(isbn);        //2.更新書的庫存        bookShopDao.updateBookStock(isbn);        //3.更新用戶余額        bookShopDao.updateUserAccount(username, price);    }}

Cashier

package com.springinaction.transaction;import java.util.List;public interface Cashier {    public void checkout(String username, List<String>isbns);}

CashierImpl:CashierImpl.checkout和bookShopService.purchase聯(lián)合測試了事務(wù)的傳播行為

package com.springinaction.transaction; import java.util.List; import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional; @Service("cashier")public class CashierImpl implements Cashier {    @Autowired    private BookShopService bookShopService;     @Transactional    @Override    public void checkout(String username, List<String> isbns) {        for(String isbn : isbns) {            bookShopService.purchase(username, isbn);        }    }}

BookStockException

package com.springinaction.transaction;public class BookStockException extends RuntimeException {     private static final long serialVersionUID = 1L;     public BookStockException() {        super();        // TODO Auto-generated constructor stub    }     public BookStockException(String arg0, Throwable arg1, boolean arg2,            boolean arg3) {        super(arg0, arg1, arg2, arg3);        // TODO Auto-generated constructor stub    }     public BookStockException(String arg0, Throwable arg1) {        super(arg0, arg1);        // TODO Auto-generated constructor stub    }     public BookStockException(String arg0) {        super(arg0);        // TODO Auto-generated constructor stub    }     public BookStockException(Throwable arg0) {        super(arg0);        // TODO Auto-generated constructor stub    }}

UserAccountException

package com.springinaction.transaction;public class UserAccountException extends RuntimeException {     private static final long serialVersionUID = 1L;     public UserAccountException() {        super();        // TODO Auto-generated constructor stub    }     public UserAccountException(String arg0, Throwable arg1, boolean arg2,            boolean arg3) {        super(arg0, arg1, arg2, arg3);        // TODO Auto-generated constructor stub    }     public UserAccountException(String arg0, Throwable arg1) {        super(arg0, arg1);        // TODO Auto-generated constructor stub    }     public UserAccountException(String arg0) {        super(arg0);        // TODO Auto-generated constructor stub    }     public UserAccountException(Throwable arg0) {        super(arg0);        // TODO Auto-generated constructor stub    }}

測試類

package com.springinaction.transaction; import java.util.Arrays; import org.junit.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext; public class SpringTransitionTest {     private ApplicationContext ctx = null;    private BookShopDao bookShopDao = null;    private BookShopService bookShopService = null;    private Cashier cashier = null;    {        ctx = new ClassPathXmlApplicationContext("config/transaction.xml");        bookShopDao = ctx.getBean(BookShopDao.class);        bookShopService = ctx.getBean(BookShopService.class);        cashier = ctx.getBean(Cashier.class);    }     @Test    public void testBookShopDaoFindPriceByIsbn() {        System.out.println(bookShopDao.findBookPriceByIsbn("1001"));    }     @Test    public void testBookShopDaoUpdateBookStock(){        bookShopDao.updateBookStock("1001");    }     @Test    public void testBookShopDaoUpdateUserAccount(){        bookShopDao.updateUserAccount("AA", 100);    }    @Test    public void testBookShopService(){        bookShopService.purchase("AA", "1001");    }     @Test    public void testTransactionPropagation(){        cashier.checkout("AA", Arrays.asList("1001", "1002"));    }}

OVER

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容