Spring 事務(wù) 詳解

1. 關(guān)于事務(wù)

事務(wù)控制就是將一系列操作當(dāng)成一個(gè)不可拆分的邏輯單元沥潭,保證這些操作要么都成功邀泉,要么都失敗。在關(guān)系數(shù)據(jù)庫中钝鸽,一個(gè)事務(wù)可以是一條SQL語句汇恤,一組SQL語句或整個(gè)程序。

事務(wù)應(yīng)該具有4個(gè)屬性:原子性拔恰、一致性因谎、隔離性、持續(xù)性颜懊。這四個(gè)屬性通常稱為ACID特性矛纹。

  • 原子性(atomicity)呐伞。一個(gè)事務(wù)是一個(gè)不可分割的工作單位,事務(wù)中包括的諸操作要么都做,要么都不做权她。
  • 一致性(consistency)。事務(wù)必須是使數(shù)據(jù)庫從一個(gè)一致性狀態(tài)變到另一個(gè)一致性狀態(tài)痒蓬。一致性與原子性是密切相關(guān)的拱雏。
  • 隔離性(isolation)。一個(gè)事務(wù)的執(zhí)行不能被其他事務(wù)干擾媳维。即一個(gè)事務(wù)內(nèi)部的操作及使用的數(shù)據(jù)對并發(fā)的其他事務(wù)是隔離的酿雪,并發(fā)執(zhí)行的各個(gè)事務(wù)之間不能互相干擾。
  • 持久性(durability)侄刽。一旦事務(wù)完成执虹,無論發(fā)生什么系統(tǒng)錯(cuò)誤,它的結(jié)果都不應(yīng)該受到影響唠梨,這樣就能從任何系統(tǒng)崩潰中恢復(fù)過來袋励。通常,事務(wù)的結(jié)果被寫到持久化存儲器中当叭。

事務(wù)有兩種操作:提交茬故,回滾

2. spring 事務(wù)核心接口

Spring事務(wù)管理涉及的接口的聯(lián)系如下:


事務(wù)接口

2.1 事務(wù)管理器

Spring并不直接管理事務(wù),而是提供了多種事務(wù)管理器蚁鳖,他們將事務(wù)管理的職責(zé)委托給Hibernate或者JTA等持久化機(jī)制所提供的相關(guān)平臺框架的事務(wù)來實(shí)現(xiàn)磺芭。
Spring事務(wù)管理器的接口是org.springframework.transaction.PlatformTransactionManager,通過這個(gè)接口醉箕,Spring為各個(gè)平臺如JDBC钾腺、Hibernate等都提供了對應(yīng)的事務(wù)管理器徙垫,但是具體的實(shí)現(xiàn)就是各個(gè)平臺自己的事情了。接口內(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ù)管理機(jī)制對Spring來說是透明的放棒,它并不關(guān)心那些姻报,那些是對應(yīng)各個(gè)平臺需要關(guān)心的,所以Spring事務(wù)管理的一個(gè)優(yōu)點(diǎn)就是為不同的事務(wù)API提供一致的編程模型间螟,如JTA吴旋、JDBC、Hibernate厢破、JPA荣瑟。下面分別介紹各個(gè)平臺框架實(shí)現(xiàn)事務(wù)管理的機(jī)制。

2.11 JDBC事務(wù)

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

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

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

2.1.2 Hibernate事務(wù)

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

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

sessionFactory屬性需要裝配一個(gè)Hibernate的session工廠孙蒙,HibernateTransactionManager的實(shí)現(xiàn)細(xì)節(jié)是它將事務(wù)管理的職責(zé)委托給org.hibernate.Transaction對象项棠,而后者是從Hibernate Session中獲取到的。當(dāng)事務(wù)成功完成時(shí)挎峦,HibernateTransactionManager將會調(diào)用Transaction對象的commit()方法香追,反之,將會調(diào)用rollback()方法坦胶。

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

Hibernate多年來一直是事實(shí)上的Java持久化標(biāo)準(zhǔn)透典,但是現(xiàn)在Java持久化API作為真正的Java持久化標(biāo)準(zhǔn)進(jìn)入大家的視野。如果你計(jì)劃使用JPA的話顿苇,那你需要使用Spring的JpaTransactionManager來處理事務(wù)峭咒。你需要在Spring中這樣配置JpaTransactionManager

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

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

2.1.4 Java原生API事務(wù)

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

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

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

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

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

事務(wù)的屬性

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

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

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

  • propagation_required:表示當(dāng)前方法必須運(yùn)行在事務(wù)中男摧,如果事務(wù)存在,方法在事務(wù)中運(yùn)行;否則鸵熟,啟動(dòng)一個(gè)新的事務(wù)
  • propagation_supports:表示當(dāng)前方法不需要事務(wù)上下文,但是如果存在當(dāng)前事務(wù)的話僵控,那么該方法會在這個(gè)事務(wù)中運(yùn)行
  • propagation_mandatory:表示該方法必須在事務(wù)中運(yùn)行香到,如果當(dāng)前事務(wù)不存在鱼冀,則會拋出一個(gè)異常
  • propagation_required_new:在該方法執(zhí)行期間报破,當(dāng)前事務(wù)會被掛起。如果使用JTATransactionManager的話千绪,則需要訪問TransactionManager
  • propagation_not_supproted:表示該方法不應(yīng)該運(yùn)行在事務(wù)中充易。如果存在當(dāng)前事務(wù),在該方法運(yùn)行期間荸型,當(dāng)前事務(wù)將被掛起盹靴。如果使用JTATransactionManager的話,則需要訪問TransactionManager.
  • propagation_never:表示當(dāng)前方法不應(yīng)該運(yùn)行在事務(wù)上下文中瑞妇。如果當(dāng)前正有一個(gè)事務(wù)在運(yùn)行稿静,則會拋出異常
  • propagation_nested:表示如果當(dāng)前已經(jīng)存在一個(gè)事務(wù),那么該方法將會在嵌套事務(wù)中運(yùn)行辕狰。嵌套的事務(wù)可以獨(dú)立于當(dāng)前事務(wù)進(jìn)行單獨(dú)地提交或回滾改备。如果當(dāng)前事務(wù)不存在,那么其行為與PROPAGATION_REQUIRED一樣蔓倍。注意各廠商對這種傳播行為的支持是有所差異的悬钳。可以參考資源管理器的文檔來確認(rèn)它們是否支持嵌套事務(wù)

(1)PROPAGATION_REQUIRED 如果存在一個(gè)事務(wù)偶翅,則支持當(dāng)前事務(wù)默勾。如果沒有事務(wù)則開啟一個(gè)新的事務(wù)。

//事務(wù)屬性 PROPAGATION_REQUIRED
methodA{
    ……
    methodB();
    ……
}
//事務(wù)屬性 PROPAGATION_REQUIRED
methodB{
   ……
}

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

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

main{ 
    metodB(); 
} 

相當(dāng)于

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)用都獲得到一個(gè)相同的連接铭段。在調(diào)用methodB時(shí),沒有一個(gè)存在的事務(wù)秦爆,所以獲得一個(gè)新的連接序愚,開啟了一個(gè)新的事務(wù)。
單獨(dú)調(diào)用MethodA時(shí)等限,在MethodA內(nèi)又會調(diào)用MethodB.

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

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

調(diào)用MethodA時(shí)爸吮,環(huán)境中沒有事務(wù),所以開啟一個(gè)新的事務(wù).當(dāng)在MethodA中調(diào)用MethodB時(shí)望门,環(huán)境中已經(jīng)有了一個(gè)事務(wù)形娇,所以methodB就加入當(dāng)前事務(wù)。

(2)PROPAGATION_SUPPORTS 如果存在一個(gè)事務(wù)筹误,支持當(dāng)前事務(wù)桐早。如果沒有事務(wù),則非事務(wù)的執(zhí)行厨剪。但是對于事務(wù)同步的事務(wù)管理器哄酝,PROPAGATION_SUPPORTS與不使用事務(wù)有少許不同。

//事務(wù)屬性 PROPAGATION_REQUIRED
methodA(){
  methodB();
}

//事務(wù)屬性 PROPAGATION_SUPPORTS
methodB(){
  ……
}

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

(3)PROPAGATION_MANDATORY 如果已經(jīng)存在一個(gè)事務(wù)直晨,支持當(dāng)前事務(wù)搀军。如果沒有一個(gè)活動(dòng)的事務(wù),則拋出異常勇皇。

//事務(wù)屬性 PROPAGATION_REQUIRED
methodA(){
    methodB();
}

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

當(dāng)單獨(dú)調(diào)用methodB時(shí)罩句,因?yàn)楫?dāng)前沒有一個(gè)活動(dòng)的事務(wù),則會拋出異常throw new IllegalTransactionStateException(“Transaction propagation ‘mandatory’ but no existing transaction found”);當(dāng)調(diào)用methodA時(shí)敛摘,methodB則加入到methodA的事務(wù)中门烂,事務(wù)地執(zhí)行。

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

//事務(wù)屬性 PROPAGATION_REQUIRED
methodA(){
    doSomeThingA();
    methodB();
    doSomeThingB();
}

//事務(wù)屬性 PROPAGATION_REQUIRES_NEW
methodB(){
    ……
}

調(diào)用A方法:

main(){
    methodA();
}```

相當(dāng)于

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

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

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

(6)PROPAGATION_NEVER 總是非事務(wù)地執(zhí)行粹断,如果存在一個(gè)活動(dòng)事務(wù)符欠,則拋出異常。

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

//事務(wù)屬性 PROPAGATION_REQUIRED
methodA(){
    doSomeThingA();
    methodB();
    doSomeThingB();
}

//事務(wù)屬性 PROPAGATION_NESTED
methodB(){
    ……
}

如果單獨(dú)調(diào)用methodB方法盾戴,則按REQUIRED屬性執(zhí)行寄锐。如果調(diào)用methodA方法兵多,相當(dāng)于下面的效果:

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 {
        //釋放資源
    }
}

當(dāng)methodB方法調(diào)用之前,調(diào)用setSavepoint方法橄仆,保存當(dāng)前的狀態(tài)到savepoint剩膘。如果methodB方法調(diào)用失敗,則恢復(fù)到之前保存的狀態(tài)盆顾。但是需要注意的是怠褐,這時(shí)的事務(wù)并沒有進(jìn)行提交,如果后續(xù)的代碼(doSomeThingB()方法)調(diào)用失敗您宪,則回滾包括methodB方法的所有操作奈懒。

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

PROPAGATION_NESTED 與PROPAGATION_REQUIRES_NEW的區(qū)別:它們非常類似,都像一個(gè)嵌套事務(wù)捏卓,如果不存在一個(gè)活動(dòng)的事務(wù)极祸,都會開啟一個(gè)新的事務(wù)。使用 PROPAGATION_REQUIRES_NEW時(shí),內(nèi)層事務(wù)與外層事務(wù)就像兩個(gè)獨(dú)立的事務(wù)一樣遥金,一旦內(nèi)層事務(wù)進(jìn)行了提交后浴捆,外層事務(wù)不能對其進(jìn)行回滾。兩個(gè)事務(wù)互不影響稿械。兩個(gè)事務(wù)不是一個(gè)真正的嵌套事務(wù)选泻。同時(shí)它需要JTA事務(wù)管理器的支持。

使用PROPAGATION_NESTED時(shí)美莫,外層事務(wù)的回滾可以引起內(nèi)層事務(wù)的回滾滔金。而內(nèi)層事務(wù)的異常并不會導(dǎo)致外層事務(wù)的回滾,它是一個(gè)真正的嵌套事務(wù)茂嗓。DataSourceTransactionManager使用savepoint支持PROPAGATION_NESTED時(shí)餐茵,需要JDBC 3.0以上驅(qū)動(dòng)及1.4以上的JDK版本支持。其它的JTA TrasactionManager實(shí)現(xiàn)可能有不同的支持方式述吸。

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

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

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

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

2.2.2 隔離級別

事務(wù)的第二個(gè)維度就是隔離級別(isolation level)。隔離級別定義了一個(gè)事務(wù)可能受其他并發(fā)事務(wù)影響的程度入撒。

在典型的應(yīng)用程序中隆豹,多個(gè)事務(wù)并發(fā)運(yùn)行,經(jīng)常會操作相同的數(shù)據(jù)來完成各自的任務(wù)茅逮。并發(fā)雖然是必須的璃赡,但可能會導(dǎo)致一下的問題。

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

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

  • 不可重復(fù)讀的重點(diǎn)是修改: 同樣的條件, 你讀取過的數(shù)據(jù), 再次讀取出來發(fā)現(xiàn)值不一樣了
  • 幻讀的重點(diǎn)在于新增或者刪除: 同樣的條件, 第1次和第2次讀出來的記錄數(shù)不一樣
  • 對于前者, 只需要鎖住滿足條件的記錄。
  • 對于后者, 要鎖住滿足條件及其相近的記錄窍箍。

隔離級別

  • isolation_default:使用后端數(shù)據(jù)庫默認(rèn)的隔離級別
  • ioslation_read_uncommitted:最低的隔離級別串纺,允許讀取尚未提交的數(shù)據(jù)變更丽旅,可能會導(dǎo)致臟讀、幻讀或不可重復(fù)讀
  • isolation_read_committed:允許讀取并發(fā)事務(wù)已經(jīng)提交的數(shù)據(jù)纺棺,可以阻止臟讀榄笙,但是幻讀或不可重復(fù)讀仍有可能發(fā)生
  • ioslation_serializable:最高的隔離級別,完全服從ACID的隔離級別祷蝌,確保阻止臟讀茅撞、不可重復(fù)讀以及幻讀,也是最慢的事務(wù)隔離級別巨朦,因?yàn)樗ǔJ峭ㄟ^完全鎖定事務(wù)相關(guān)的數(shù)據(jù)庫表來實(shí)現(xiàn)的

2.2.3 只讀

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

2.2.4 事務(wù)超時(shí)

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

2.2.5 回滾規(guī)則

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

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

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

3 編程式事務(wù)

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

Spring提供了對編程式事務(wù)和聲明式事務(wù)的支持淤击,編程式事務(wù)允許用戶在代碼中精確定義事務(wù)的邊界匠抗,而聲明式事務(wù)(基于AOP)有助于用戶將操作與事務(wù)規(guī)則進(jìn)行解耦故源。

簡單地說污抬,編程式事務(wù)侵入到了業(yè)務(wù)代碼里面,但是提供了更加詳細(xì)的事務(wù)管理绳军;而聲明式事務(wù)由于基于AOP印机,所以既能起到事務(wù)管理的作用,又可以不影響業(yè)務(wù)代碼的具體實(shí)現(xiàn)门驾。

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

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

3.2.1 使用TransactionTemplate

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

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

使用TransactionCallback()可以返回一個(gè)值沮趣。如果使用TransactionCallbackWithoutResult則沒有返回值屯烦。

3.2.2 使用PlatformTransactionManager

DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); //定義一個(gè)某個(gè)框架平臺的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 配置方式

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

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

<?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共享一個(gè)代理基類

<?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標(biāo)簽配置的攔截器

<?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" /> <!-- 設(shè)置全局掃描的范圍 -->

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

此時(shí)在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 一個(gè)聲明式事務(wù)的實(shí)例

首先是數(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/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.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">

    <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ù)書號獲取書的單價(jià)
    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ù)的傳播行為,即當(dāng)前的事務(wù)方法被另外一個(gè)事務(wù)方法調(diào)用時(shí)如何使用事務(wù)计贰。
     * 默認(rèn)取值為REQUIRED钦睡,即使用調(diào)用方法的事務(wù)
     * REQUIRES_NEW:使用自己的事務(wù),調(diào)用的事務(wù)方法的事務(wù)被掛起躁倒。
     *
     * 2.使用isolation 指定事務(wù)的隔離級別荞怒,最常用的取值為READ_COMMITTED
     * 3.默認(rèn)情況下 Spring 的聲明式事務(wù)對所有的運(yùn)行時(shí)異常進(jìn)行回滾,也可以通過對應(yīng)的屬性進(jìn)行設(shè)置秧秉。通常情況下褐桌,默認(rèn)值即可。
     * 4.使用readOnly 指定事務(wù)是否為只讀象迎。 表示這個(gè)事務(wù)只讀取數(shù)據(jù)但不更新數(shù)據(jù)荧嵌,這樣可以幫助數(shù)據(jù)庫引擎優(yōu)化事務(wù)。若真的是一個(gè)只讀取數(shù)據(jù)庫值得方法砾淌,應(yīng)設(shè)置readOnly=true
     * 5.使用timeOut 指定強(qiáng)制回滾之前事務(wù)可以占用的時(shí)間啦撮。
     */
    @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.獲取書的單價(jià)
        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"));
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市汪厨,隨后出現(xiàn)的幾起案子赃春,更是在濱河造成了極大的恐慌,老刑警劉巖劫乱,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件织中,死亡現(xiàn)場離奇詭異锥涕,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)狭吼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門站楚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人搏嗡,你說我怎么就攤上這事窿春。” “怎么了采盒?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵旧乞,是天一觀的道長。 經(jīng)常有香客問我磅氨,道長尺栖,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任烦租,我火速辦了婚禮延赌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘叉橱。我一直安慰自己挫以,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布窃祝。 她就那樣靜靜地躺著掐松,像睡著了一般。 火紅的嫁衣襯著肌膚如雪粪小。 梳的紋絲不亂的頭發(fā)上大磺,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機(jī)與錄音探膊,去河邊找鬼杠愧。 笑死,一個(gè)胖子當(dāng)著我的面吹牛逞壁,可吹牛的內(nèi)容都是我干的流济。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼猾担,長吁一口氣:“原來是場噩夢啊……” “哼袭灯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起绑嘹,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎橘茉,沒想到半個(gè)月后工腋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體姨丈,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年擅腰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蟋恬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡趁冈,死狀恐怖歼争,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情渗勘,我是刑警寧澤沐绒,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站旺坠,受9級特大地震影響乔遮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜取刃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一蹋肮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧璧疗,春花似錦坯辩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至啦膜,卻和暖如春有送,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背僧家。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工雀摘, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人八拱。 一個(gè)月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓阵赠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親肌稻。 傳聞我的和親對象是個(gè)殘疾皇子清蚀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容

  • 很多人喜歡這篇文章,特此同步過來 由淺入深談?wù)搒pring事務(wù) 前言 這篇其實(shí)也要?dú)w納到《常識》系列中爹谭,但這重點(diǎn)又...
    碼農(nóng)戲碼閱讀 4,706評論 2 59
  • 事務(wù)接口定義 在Spring中枷邪,事務(wù)是通過TransactionDefinition接口定義的。其中定義了訪問事務(wù)...
    追夢人Plus閱讀 1,154評論 0 12
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,748評論 6 342
  • Spring事務(wù)機(jī)制主要包括聲明式事務(wù)和編程式事務(wù)诺凡,此處側(cè)重講解聲明式事務(wù)东揣,編程式事務(wù)在實(shí)際開發(fā)中得不到廣泛使用践惑,...
    EnigmaXXX閱讀 667評論 0 0
  • 5.Spring的事務(wù) 通常情況下,J2EE有2種事務(wù)管理方式:全局事務(wù)和本地事務(wù)嘶卧,2種事務(wù)都比較明顯的缺陷尔觉。 全...
    FTOLsXD閱讀 1,499評論 0 8