1 初步理解
理解事務(wù)之前,先講一個(gè)你日常生活中最常干的事:取錢。
比如你去ATM機(jī)取1000塊錢,大體有兩個(gè)步驟:首先輸入密碼金額,銀行卡扣掉1000元錢;然后ATM出1000元錢。這兩個(gè)步驟必須是要么都執(zhí)行要么都不執(zhí)行。如果銀行卡扣除了1000塊但是ATM出錢失敗的話,你將會(huì)損失1000元;如果銀行卡扣錢失敗但是ATM卻出了1000塊,那么銀行將損失1000元。所以,如果一個(gè)步驟成功另一個(gè)步驟失敗對(duì)雙方都不是好事,如果不管哪一個(gè)步驟失敗了以后,整個(gè)取錢過(guò)程都能回滾,也就是完全取消所有操作的話,這對(duì)雙方都是極好的。
事務(wù)就是用來(lái)解決類似問(wèn)題的。事務(wù)是一系列的動(dòng)作,它們綜合在一起才是一個(gè)完整的工作單元,這些動(dòng)作必須全部完成,如果有一個(gè)失敗的話,那么事務(wù)就會(huì)回滾到最開(kāi)始的狀態(tài),仿佛什么都沒(méi)發(fā)生過(guò)一樣。
在企業(yè)級(jí)應(yīng)用程序開(kāi)發(fā)中,事務(wù)管理必不可少的技術(shù),用來(lái)確保數(shù)據(jù)的完整性和一致性。
事務(wù)有四個(gè)特性:ACID
原子性(Atomicity):事務(wù)是一個(gè)原子操作,由一系列動(dòng)作組成。事務(wù)的原子性確保動(dòng)作要么全部完成,要么完全不起作用。
一致性(Consistency):一旦事務(wù)完成(不管成功還是失敗),系統(tǒng)必須確保它所建模的業(yè)務(wù)處于一致的狀態(tài),而不會(huì)是部分完成部分失敗。在現(xiàn)實(shí)中的數(shù)據(jù)不應(yīng)該被破壞。
隔離性(Isolation):可能有許多事務(wù)會(huì)同時(shí)處理相同的數(shù)據(jù),因此每個(gè)事務(wù)都應(yīng)該與其他事務(wù)隔離開(kāi)來(lái),防止數(shù)據(jù)損壞。
持久性(Durability):一旦事務(wù)完成,無(wú)論發(fā)生什么系統(tǒng)錯(cuò)誤,它的結(jié)果都不應(yīng)該受到影響,這樣就能從任何系統(tǒng)崩潰中恢復(fù)過(guò)來(lái)。通常情況下,事務(wù)的結(jié)果被寫(xiě)到持久化存儲(chǔ)器中。
2 核心接口
Spring事務(wù)管理的實(shí)現(xiàn)有許多細(xì)節(jié),如果對(duì)整個(gè)接口框架有個(gè)大體了解會(huì)非常有利于我們理解事務(wù),下面通過(guò)講解Spring的事務(wù)接口來(lái)了解Spring實(shí)現(xiàn)事務(wù)的具體策略。
Spring事務(wù)管理涉及的接口的聯(lián)系如下:
2.1 事務(wù)管理器
Spring并不直接管理事務(wù),而是提供了多種事務(wù)管理器,他們將事務(wù)管理的職責(zé)委托給Hibernate或者JTA等持久化機(jī)制所提供的相關(guān)平臺(tái)框架的事務(wù)來(lái)實(shí)現(xiàn)。
Spring事務(wù)管理器的接口是org.springframework.transaction.PlatformTransactionManager,通過(guò)這個(gè)接口,Spring為各個(gè)平臺(tái)如JDBC、Hibernate等都提供了對(duì)應(yīng)的事務(wù)管理器,但是具體的實(shí)現(xiàn)就是各個(gè)平臺(tái)自己的事情了。此接口的內(nèi)容如下:
Public interface PlatformTransactionManager()…{
// 由TransactionDefinition得到TransactionStatus對(duì)象
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
// 提交
Void commit(TransactionStatus status) throws TransactionException;
// 回滾
Void rollback(TransactionStatus status) throws TransactionException;
}
從這里可知具體的具體的事務(wù)管理機(jī)制對(duì)Spring來(lái)說(shuō)是透明的,它并不關(guān)心那些,那些是對(duì)應(yīng)各個(gè)平臺(tái)需要關(guān)心的,所以Spring事務(wù)管理的一個(gè)優(yōu)點(diǎn)就是為不同的事務(wù)API提供一致的編程模型,如JTA、JDBC、Hibernate、JPA。下面分別介紹各個(gè)平臺(tái)框架實(shí)現(xiàn)事務(wù)管理的機(jī)制。
2.1.1 JDBC事務(wù)
如果應(yīng)用程序中直接使用JDBC來(lái)進(jìn)行持久化,DataSourceTransactionManager會(huì)為你處理事務(wù)邊界。為了使用DataSourceTransactionManager,你需要使用如下的XML將其裝配到應(yīng)用程序的上下文定義中:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
實(shí)際上,DataSourceTransactionManager是通過(guò)調(diào)用java.sql.Connection來(lái)管理事務(wù),而后者是通過(guò)DataSource獲取到的。通過(guò)調(diào)用連接的commit()方法來(lái)提交事務(wù),同樣,事務(wù)失敗則通過(guò)調(diào)用rollback()方法進(jìn)行回滾。
2.1.2 Hibernate事務(wù)
如果應(yīng)用程序的持久化是通過(guò)Hibernate實(shí)習(xí)的,那么你需要使用HibernateTransactionManager。對(duì)于Hibernate3,需要在Spring上下文定義中添加如下的聲明:
<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對(duì)象,而后者是從Hibernate Session中獲取到的。當(dāng)事務(wù)成功完成時(shí),HibernateTransactionManager將會(huì)調(diào)用Transaction對(duì)象的commit()方法,反之,將會(huì)調(diào)用rollback()方法。
2.1.3 Java持久化API事務(wù)(JPA)
Hibernate多年來(lái)一直是事實(shí)上的Java持久化標(biāo)準(zhǔn),但是現(xiàn)在Java持久化API作為真正的Java持久化標(biāo)準(zhǔn)進(jìn)入大家的視野。如果你計(jì)劃使用JPA的話,那你需要使用Spring的JpaTransactionManager來(lái)處理事務(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合作來(lái)構(gòu)建事務(wù)。
2.1.4 Java原生API事務(wù)
如果你沒(méi)有使用以上所述的事務(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.UserTransaction和javax.transaction.TransactionManager對(duì)象,其中事務(wù)成功完成通過(guò)UserTransaction.commit()方法提交,事務(wù)失敗通過(guò)UserTransaction.rollback()方法回滾。
2.2 基本事務(wù)屬性的定義
上面講到的事務(wù)管理器接口PlatformTransactionManager通過(guò)getTransaction(TransactionDefinition definition)方法來(lái)得到事務(wù),這個(gè)方法里面的參數(shù)是TransactionDefinition類,這個(gè)類就定義了一些基本的事務(wù)屬性。
那么什么是事務(wù)屬性呢?事務(wù)屬性可以理解成事務(wù)的一些基本配置,描述了事務(wù)策略如何應(yīng)用到方法上。事務(wù)屬性包含了5個(gè)方面,如圖所示:
而TransactionDefinition接口內(nèi)容如下:
public interface TransactionDefinition {
int getPropagationBehavior(); // 返回事務(wù)的傳播行為
int getIsolationLevel(); // 返回事務(wù)的隔離級(jí)別,事務(wù)管理器根據(jù)它來(lái)控制另外一個(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正好用來(lái)定義事務(wù)屬性,下面詳細(xì)介紹一下各個(gè)事務(wù)屬性。
2.2.1 傳播行為
事務(wù)的第一個(gè)方面是傳播行為(propagation behavior)。當(dāng)事務(wù)方法被另一個(gè)事務(wù)方法調(diào)用時(shí),必須指定事務(wù)應(yīng)該如何傳播。例如:方法可能繼續(xù)在現(xiàn)有事務(wù)中運(yùn)行,也可能開(kāi)啟一個(gè)新事務(wù),并在自己的事務(wù)中運(yùn)行。Spring定義了七種傳播行為:
傳播行為 含義
PROPAGATION_REQUIRED 表示當(dāng)前方法必須運(yùn)行在事務(wù)中。如果當(dāng)前事務(wù)存在,方法將會(huì)在該事務(wù)中運(yùn)行。否則,會(huì)啟動(dòng)一個(gè)新的事務(wù)
PROPAGATION_SUPPORTS 表示當(dāng)前方法不需要事務(wù)上下文,但是如果存在當(dāng)前事務(wù)的話,那么該方法會(huì)在這個(gè)事務(wù)中運(yùn)行
PROPAGATION_MANDATORY 表示該方法必須在事務(wù)中運(yùn)行,如果當(dāng)前事務(wù)不存在,則會(huì)拋出一個(gè)異常
PROPAGATION_REQUIRED_NEW 表示當(dāng)前方法必須運(yùn)行在它自己的事務(wù)中。一個(gè)新的事務(wù)將被啟動(dòng)。如果存在當(dāng)前事務(wù),在該方法執(zhí)行期間,當(dāng)前事務(wù)會(huì)被掛起。如果使用JTATransactionManager的話,則需要訪問(wèn)TransactionManager
PROPAGATION_NOT_SUPPORTED 表示該方法不應(yīng)該運(yùn)行在事務(wù)中。如果存在當(dāng)前事務(wù),在該方法運(yùn)行期間,當(dāng)前事務(wù)將被掛起。如果使用JTATransactionManager的話,則需要訪問(wèn)TransactionManager
PROPAGATION_NEVER 表示當(dāng)前方法不應(yīng)該運(yùn)行在事務(wù)上下文中。如果當(dāng)前正有一個(gè)事務(wù)在運(yùn)行,則會(huì)拋出異常
PROPAGATION_NESTED 表示如果當(dāng)前已經(jīng)存在一個(gè)事務(wù),那么該方法將會(huì)在嵌套事務(wù)中運(yùn)行。嵌套的事務(wù)可以獨(dú)立于當(dāng)前事務(wù)進(jìn)行單獨(dú)地提交或回滾。如果當(dāng)前事務(wù)不存在,那么其行為與PROPAGATION_REQUIRED一樣。注意各廠商對(duì)這種傳播行為的支持是有所差異的。可以參考資源管理器的文檔來(lái)確認(rèn)它們是否支持嵌套事務(wù)
注:以下具體講解傳播行為的內(nèi)容參考自Spring事務(wù)機(jī)制詳解
(1)PROPAGATION_REQUIRED 如果存在一個(gè)事務(wù),則支持當(dāng)前事務(wù)。如果沒(méi)有事務(wù)則開(kāi)啟一個(gè)新的事務(wù)。
//事務(wù)屬性 PROPAGATION_REQUIRED
methodA{
……
methodB();
……
}
//事務(wù)屬性 PROPAGATION_REQUIRED
methodB{
……
}
使用spring聲明式事務(wù),spring使用AOP來(lái)支持聲明式事務(wù),會(huì)根據(jù)事務(wù)屬性,自動(dòng)在方法調(diào)用之前決定是否開(kāi)啟一個(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í),沒(méi)有一個(gè)存在的事務(wù),所以獲得一個(gè)新的連接,開(kāi)啟了一個(gè)新的事務(wù)。
單獨(dú)調(diào)用MethodA時(shí),在MethodA內(nèi)又會(huì)調(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)境中沒(méi)有事務(wù),所以開(kāi)啟一個(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ù)。如果沒(méi)有事務(wù),則非事務(wù)的執(zhí)行。但是對(duì)于事務(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ù)。如果沒(méi)有一個(gè)活動(dòng)的事務(wù),則拋出異常。
//事務(wù)屬性 PROPAGATION_REQUIRED
methodA(){
methodB();
}
//事務(wù)屬性 PROPAGATION_MANDATORY
methodB(){
……
}
當(dāng)單獨(dú)調(diào)用methodB時(shí),因?yàn)楫?dāng)前沒(méi)有一個(gè)活動(dòng)的事務(wù),則會(huì)拋出異常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 總是開(kāi)啟一個(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();//開(kāi)啟一個(gè)新的事務(wù)
Transaction ts1 = tm.getTransaction();
doSomeThing();
tm.suspend();//掛起當(dāng)前事務(wù)
try{
tm.begin();//重新開(kāi)啟第二個(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ù)中. 如果沒(méi)有活動(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ù)并沒(méi)有進(jìn)行提交,如果后續(xù)的代碼(doSomeThingB()方法)調(diào)用失敗,則回滾包括methodB方法的所有操作。
嵌套事務(wù)一個(gè)非常重要的概念就是內(nèi)層事務(wù)依賴于外層事務(wù)。外層事務(wù)失敗時(shí),會(huì)回滾內(nèi)層事務(wù)所做的動(dòng)作。而內(nèi)層事務(wù)操作失敗并不會(huì)引起外層事務(wù)的回滾。
PROPAGATION_NESTED 與PROPAGATION_REQUIRES_NEW的區(qū)別:它們非常類似,都像一個(gè)嵌套事務(wù),如果不存在一個(gè)活動(dòng)的事務(wù),都會(huì)開(kāi)啟一個(gè)新的事務(wù)。使用 PROPAGATION_REQUIRES_NEW時(shí),內(nèi)層事務(wù)與外層事務(wù)就像兩個(gè)獨(dú)立的事務(wù)一樣,一旦內(nèi)層事務(wù)進(jìn)行了提交后,外層事務(wù)不能對(duì)其進(jìn)行回滾。兩個(gè)事務(wù)互不影響。兩個(gè)事務(wù)不是一個(gè)真正的嵌套事務(wù)。同時(shí)它需要JTA事務(wù)管理器的支持。
使用PROPAGATION_NESTED時(shí),外層事務(wù)的回滾可以引起內(nèi)層事務(wù)的回滾。而內(nèi)層事務(wù)的異常并不會(huì)導(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ù)開(kāi)始執(zhí)行時(shí), 外部事務(wù)將被掛起, 內(nèi)務(wù)事務(wù)結(jié)束時(shí), 外部事務(wù)將繼續(xù)執(zhí)行。
另一方面, PROPAGATION_NESTED 開(kāi)始一個(gè) “嵌套的” 事務(wù), 它是已經(jīng)存在事務(wù)的一個(gè)真正的子事務(wù). 潛套事務(wù)開(kāi)始執(zhí)行時(shí), 它將取得一個(gè) savepoint. 如果這個(gè)嵌套事務(wù)失敗, 我們將回滾到此 savepoint. 潛套事務(wù)是外部事務(wù)的一部分, 只有外部事務(wù)結(jié)束后它才會(huì)被提交。
由此可見(jiàn), PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大區(qū)別在于, PROPAGATION_REQUIRES_NEW 完全是一個(gè)新的事務(wù), 而 PROPAGATION_NESTED 則是外部事務(wù)的子事務(wù), 如果外部事務(wù) commit, 嵌套事務(wù)也會(huì)被 commit, 這個(gè)規(guī)則同樣適用于 roll back.
PROPAGATION_REQUIRED應(yīng)該是我們首先的事務(wù)傳播行為。它能夠滿足我們大多數(shù)的事務(wù)需求。
2.2.2 隔離級(jí)別
事務(wù)的第二個(gè)維度就是隔離級(jí)別(isolation level)。隔離級(jí)別定義了一個(gè)事務(wù)可能受其他并發(fā)事務(wù)影響的程度。
(1)并發(fā)事務(wù)引起的問(wèn)題
在典型的應(yīng)用程序中,多個(gè)事務(wù)并發(fā)運(yùn)行,經(jīng)常會(huì)操作相同的數(shù)據(jù)來(lái)完成各自的任務(wù)。并發(fā)雖然是必須的,但可能會(huì)導(dǎo)致一下的問(wèn)題。
臟讀(Dirty reads)——臟讀發(fā)生在一個(gè)事務(wù)讀取了另一個(gè)事務(wù)改寫(xiě)但尚未提交的數(shù)據(jù)時(shí)。如果改寫(xiě)在稍后被回滾了,那么第一個(gè)事務(wù)獲取的數(shù)據(jù)就是無(wú)效的。
不可重復(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)就會(huì)發(fā)現(xiàn)多了一些原本不存在的記錄。
不可重復(fù)讀與幻讀的區(qū)別
不可重復(fù)讀的重點(diǎn)是修改:
同樣的條件, 你讀取過(guò)的數(shù)據(jù), 再次讀取出來(lái)發(fā)現(xiàn)值不一樣了
例如:在事務(wù)1中,Mary 讀取了自己的工資為1000,操作并沒(méi)有完成
con1 = getConnection();
select salary from employee empId ="Mary";
在事務(wù)2中,這時(shí)財(cái)務(wù)人員修改了Mary的工資為2000,并提交了事務(wù).
con2 = getConnection();
update employee set salary = 2000;
con2.commit();
在事務(wù)1中,Mary 再次讀取自己的工資時(shí),工資變?yōu)榱?000
//con1
select salary from employee empId ="Mary";
在一個(gè)事務(wù)中前后兩次讀取的結(jié)果并不一致,導(dǎo)致了不可重復(fù)讀。
幻讀的重點(diǎn)在于新增或者刪除:
同樣的條件, 第1次和第2次讀出來(lái)的記錄數(shù)不一樣
例如:目前工資為1000的員工有10人。事務(wù)1,讀取所有工資為1000的員工。
con1 = getConnection();
Select * from employee where salary =1000;
共讀取10條記錄
這時(shí)另一個(gè)事務(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é)果來(lái)看, 似乎不可重復(fù)讀和幻讀都表現(xiàn)為兩次讀取的結(jié)果不一致。但如果你從控制的角度來(lái)看, 兩者的區(qū)別就比較大。
對(duì)于前者, 只需要鎖住滿足條件的記錄。
對(duì)于后者, 要鎖住滿足條件及其相近的記錄。
(2)隔離級(jí)別
隔離級(jí)別 含義
ISOLATION_DEFAULT 使用后端數(shù)據(jù)庫(kù)默認(rèn)的隔離級(jí)別
ISOLATION_READ_UNCOMMITTED 最低的隔離級(jí)別,允許讀取尚未提交的數(shù)據(jù)變更,可能會(huì)導(dǎo)致臟讀、幻讀或不可重復(fù)讀
ISOLATION_READ_COMMITTED 允許讀取并發(fā)事務(wù)已經(jīng)提交的數(shù)據(jù),可以阻止臟讀,但是幻讀或不可重復(fù)讀仍有可能發(fā)生
ISOLATION_REPEATABLE_READ 對(duì)同一字段的多次讀取結(jié)果都是一致的,除非數(shù)據(jù)是被本身事務(wù)自己所修改,可以阻止臟讀和不可重復(fù)讀,但幻讀仍有可能發(fā)生
ISOLATION_SERIALIZABLE 最高的隔離級(jí)別,完全服從ACID的隔離級(jí)別,確保阻止臟讀、不可重復(fù)讀以及幻讀,也是最慢的事務(wù)隔離級(jí)別,因?yàn)樗ǔJ峭ㄟ^(guò)完全鎖定事務(wù)相關(guān)的數(shù)據(jù)庫(kù)表來(lái)實(shí)現(xiàn)的
2.2.3 只讀
事務(wù)的第三個(gè)特性是它是否為只讀事務(wù)。如果事務(wù)只對(duì)后端的數(shù)據(jù)庫(kù)進(jìn)行該操作,數(shù)據(jù)庫(kù)可以利用事務(wù)的只讀特性來(lái)進(jìn)行一些特定的優(yōu)化。通過(guò)將事務(wù)設(shè)置為只讀,你就可以給數(shù)據(jù)庫(kù)一個(gè)機(jī)會(huì),讓它應(yīng)用它認(rèn)為合適的優(yōu)化措施。
2.2.4 事務(wù)超時(shí)
為了使應(yīng)用程序很好地運(yùn)行,事務(wù)不能運(yùn)行太長(zhǎng)的時(shí)間。因?yàn)槭聞?wù)可能涉及對(duì)后端數(shù)據(jù)庫(kù)的鎖定,所以長(zhǎng)時(shí)間的事務(wù)會(huì)不必要的占用數(shù)據(jù)庫(kù)資源。事務(wù)超時(shí)就是事務(wù)的一個(gè)定時(shí)器,在特定時(shí)間內(nèi)事務(wù)如果沒(méi)有執(zhí)行完畢,那么就會(huì)自動(dòng)回滾,而不是一直等待其結(jié)束。
2.2.5 回滾規(guī)則
事務(wù)五邊形的最后一個(gè)方面是一組規(guī)則,這些規(guī)則定義了哪些異常會(huì)導(dǎo)致事務(wù)回滾而哪些不會(huì)。默認(rèn)情況下,事務(wù)只有遇到運(yùn)行期異常時(shí)才會(huì)回滾,而在遇到檢查型異常時(shí)不會(huì)回滾(這一行為與EJB的回滾行為是一致的)
但是你可以聲明事務(wù)在遇到特定的檢查型異常時(shí)像遇到運(yùn)行期異常那樣回滾。同樣,你還可以聲明事務(wù)遇到特定的異常不回滾,即使這些異常是運(yùn)行期異常。
2.3 事務(wù)狀態(tài)
上面講到的調(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ù)提供簡(jiǎn)單的控制事務(wù)執(zhí)行和查詢事務(wù)狀態(tài)的方法,在回滾或提交的時(shí)候需要應(yīng)用對(duì)應(yīng)的事務(wù)狀態(tài)。
3 編程式事務(wù)
3.1 編程式和聲明式事務(wù)的區(qū)別
Spring提供了對(duì)編程式事務(wù)和聲明式事務(wù)的支持,編程式事務(wù)允許用戶在代碼中精確定義事務(wù)的邊界,而聲明式事務(wù)(基于AOP)有助于用戶將操作與事務(wù)規(guī)則進(jìn)行解耦。
簡(jiǎn)單地說(shuō),編程式事務(wù)侵入到了業(yè)務(wù)代碼里面,但是提供了更加詳細(xì)的事務(wù)管理;而聲明式事務(wù)由于基于AOP,所以既能起到事務(wù)管理的作用,又可以不影響業(yè)務(wù)代碼的具體實(shí)現(xiàn)。
3.2 如何實(shí)現(xiàn)編程式事務(wù)?
Spring提供兩種方式的編程式事務(wù)管理,分別是:使用TransactionTemplate和直接使用PlatformTransactionManager。
3.2.1 使用TransactionTemplate
采用TransactionTemplate和采用其他Spring模板,如JdbcTempalte和HibernateTemplate是一樣的方法。它使用回調(diào)方法,把應(yīng)用程序從處理取得和釋放資源中解脫出來(lái)。如同其他模板,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則沒(méi)有返回值。
3.2.2 使用PlatformTransactionManager
示例代碼如下:
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); //定義一個(gè)某個(gè)框架平臺(tái)的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ù)庫(kù)操作
dataSourceTransactionManager.commit(status);// 提交
} catch (Exception e) {
dataSourceTransactionManager.rollback(status);// 回滾
}
4 聲明式事務(wù)
4.1 配置方式
注:以下配置代碼參考自Spring事務(wù)配置的五種方式
根據(jù)代理機(jī)制的不同,總結(jié)了五種Spring事務(wù)的配置方式,配置文件如下:
(1)每個(gè)Bean都有一個(gè)代理
<?xml version="1.0" encoding="UTF-8"?>
<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>
(2)所有Bean共享一個(gè)代理基類
<?xml version="1.0" encoding="UTF-8"?>
<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>
(3)使用攔截器
<?xml version="1.0" encoding="UTF-8"?>
<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>
(4)使用tx標(biāo)簽配置的攔截器
<?xml version="1.0" encoding="UTF-8"?>
<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>
(5)全注解
<?xml version="1.0" encoding="UTF-8"?>
<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>
此時(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í)例參考自Spring中的事務(wù)管理實(shí)例詳解
首先是數(shù)據(jù)庫(kù)表
book(isbn, book_name, price)
account(username, balance)
book_stock(isbn, stock)
然后是XML配置
<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>
使用的類
BookShopDao
package com.springinaction.transaction;
public interface BookShopDao {
// 根據(jù)書(shū)號(hào)獲取書(shū)的單價(jià)
public int findBookPriceByIsbn(String isbn);
// 更新書(shū)的庫(kù)存,使書(shū)號(hào)對(duì)應(yīng)的庫(kù)存-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) {
//檢查書(shū)的庫(kù)存是否足夠,若不夠,則拋出異常
String sql2 = "SELECT stock FROM book_stock WHERE isbn = ?";
int stock = JdbcTemplate.queryForObject(sql2, Integer.class, isbn);
if (stock == 0) {
throw new BookStockException("庫(kù)存不足!");
}
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ù)的隔離級(jí)別,最常用的取值為READ_COMMITTED
* 3.默認(rèn)情況下 Spring 的聲明式事務(wù)對(duì)所有的運(yùn)行時(shí)異常進(jìn)行回滾,也可以通過(guò)對(duì)應(yīng)的屬性進(jìn)行設(shè)置。通常情況下,默認(rèn)值即可。
* 4.使用readOnly 指定事務(wù)是否為只讀。 表示這個(gè)事務(wù)只讀取數(shù)據(jù)但不更新數(shù)據(jù),這樣可以幫助數(shù)據(jù)庫(kù)引擎優(yōu)化事務(wù)。若真的是一個(gè)只讀取數(shù)據(jù)庫(kù)值得方法,應(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.獲取書(shū)的單價(jià)
int price = bookShopDao.findBookPriceByIsbn(isbn);
//2.更新書(shū)的庫(kù)存
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, Listisbns);
}
CashierImpl:CashierImpl.checkout和bookShopService.purchase聯(lián)合測(cè)試了事務(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
}
}
測(cè)試類
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
來(lái)源:http://www./content-4-160701.html