事務有四個特性:ACID
- 原子性(Atomicity):事務是一個原子操作到涂,由一系列動作組成吭产。事務的原子性確保動作要么全部完成圾结,要么完全不起作用格二。
- 一致性(Consistency):一旦事務完成(不管成功還是失斉搿),系統(tǒng)必須確保它所建模的業(yè)務處于一致的狀態(tài)顶猜,而不會是部分完成部分失敗沧奴。在現(xiàn)實中的數(shù)據(jù)不應該被破壞。
- 隔離性(Isolation):可能有許多事務會同時處理相同的數(shù)據(jù)长窄,因此每個事務都應該與其他事務隔離開來滔吠,防止數(shù)據(jù)損壞。
- 持久性(Durability):一旦事務完成挠日,無論發(fā)生什么系統(tǒng)錯誤疮绷,它的結(jié)果都不應該受到影響,這樣就能從任何系統(tǒng)崩潰中恢復過來嚣潜。通常情況下冬骚,事務的結(jié)果被寫到持久化存儲器中。
spring事務類圖
Spring 事務屬性分析
在 Spring 中,事務是通過 TransactionDefinition 接口來定義的只冻。該接口包含與事務屬性有關的方法庇麦。
public interface TransactionDefinition{
int getIsolationLevel();
int getPropagationBehavior();
int getTimeout();
boolean isReadOnly();
}
事務隔離級別
隔離級別是指若干個并發(fā)的事務之間的隔離程度。TransactionDefinition 接口中定義了五個表示隔離級別的常量:
- TransactionDefinition.ISOLATION_DEFAULT:這是默認值喜德,表示使用底層數(shù)據(jù)庫的默認隔離級別山橄。對大部分數(shù)據(jù)庫而言,通常這值就是TransactionDefinition.ISOLATION_READ_COMMITTED舍悯。
- 讀未提交:TransactionDefinition.ISOLATION_READ_UNCOMMITTED:該隔離級別表示一個事務可以讀取另一個事務修改但還沒有提交的數(shù)據(jù)航棱。該級別不能防止臟讀和不可重復讀,因此很少使用該隔離級別萌衬。
- TransactionDefinition.ISOLATION_READ_COMMITTED:該隔離級別表示一個事務只能讀取另一個事務已經(jīng)提交的數(shù)據(jù)饮醇。該級別可以防止臟讀,這也是大多數(shù)情況下的推薦值奄薇。
- TransactionDefinition.ISOLATION_REPEATABLE_READ:該隔離級別表示一個事務在整個過程中可以多次重復執(zhí)行某個查詢驳阎,并且每次返回的記錄都相同。即使在多次查詢之間有新增的數(shù)據(jù)滿足該查詢馁蒂,這些新增的記錄也會被忽略呵晚。該級別可以防止臟讀和不可重復讀。
- TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事務依次逐個執(zhí)行沫屡,這樣事務之間就完全不可能產(chǎn)生干擾饵隙,也就是說,該級別可以防止臟讀沮脖、不可重復讀以及幻讀金矛。但是這將嚴重影響程序的性能。通常情況下也不會用到該級別勺届。
臟讀驶俊、不可重復讀、幻讀
臟讀 :所謂的臟讀免姿,其實就是讀到了別的事務回滾前的臟數(shù)據(jù)饼酿。比如事務B執(zhí)行過程中修改了數(shù)據(jù)X,在未提交前胚膊,事務A讀取了X故俐,而事務B卻回滾了,這樣事務A就形成了臟讀紊婉。
不可重復讀 :不可重復讀字面含義已經(jīng)很明了了药版,比如事務A首先讀取了一條數(shù)據(jù),然后執(zhí)行邏輯的時候喻犁,事務B將這條數(shù)據(jù)改變了槽片,然后事務A再次讀取的時候何缓,發(fā)現(xiàn)數(shù)據(jù)不匹配了,就是所謂的不可重復讀了筐乳。
幻讀 :事務A首先根據(jù)條件索引得到10條數(shù)據(jù)歌殃,然后事務B增加了數(shù)據(jù)庫一條數(shù)據(jù)乔妈,導致也符合事務A當時的搜索條件蝙云,這樣事務A再次搜索發(fā)現(xiàn)有11條數(shù)據(jù)了,就產(chǎn)生了幻讀路召。
事務傳播行為
事務傳播行為(函數(shù)調(diào)用勃刨,如何處理事務)
所謂事務的傳播行為是指,如果在開始當前事務之前股淡,一個事務上下文已經(jīng)存在身隐,此時有若干選項可以指定一個事務性方法的執(zhí)行行為。在TransactionDefinition定義中包括了如下幾個表示傳播行為的常量:
//事務屬性 PROPAGATION_REQUIRED
methodA{
……
methodB();
……
}
//事務屬性 PROPAGATION_REQUIRED
methodB{
……
}
- 1.TransactionDefinition.PROPAGATION_REQUIRED(默認的spring事務傳播級別):如果當前存在事務唯灵,則加入該事務贾铝;如果當前沒有事務,則創(chuàng)建一個新的事務埠帕。這個級別通常能滿足處理大多數(shù)的業(yè)務場景垢揩。
main{
Connection con = null;
try{
con = getConnection();
methodA(); //單獨調(diào)用MethodA時,在MethodA內(nèi)又會調(diào)用MethodB
con.commit();
} catch(RuntimeException ex) {
con.rollback();
} finally {
closeCon();
}
}
- 2.TransactionDefinition.PROPAGATION_REQUIRES_NEW:從字面即可知道敛瓷,new叁巨,每次都要一個新事務,該傳播級別的特點是呐籽,每次都會新建一個事務锋勺,并且如果當前有事務,則將上下文中的事務掛起狡蝶,執(zhí)行當前新建事務完成以后庶橱,上下文事務恢復再執(zhí)行。
main(){
TransactionManager tm = null;
try{
//獲得一個JTA事務管理器
tm = getTransactionManager();
tm.begin();//開啟一個新的事務
Transaction ts1 = tm.getTransaction();
doSomeThing();
tm.suspend();//掛起當前事務
try{
tm.begin();//重新開啟第二個事務
Transaction ts2 = tm.getTransaction();
methodB();
ts2.commit();//提交第二個事務
} Catch(RunTimeException ex) {
ts2.rollback();//回滾第二個事務
} finally {
//釋放資源
}
//methodB執(zhí)行完后贪惹,恢復第一個事務
tm.resume(ts1);
doSomeThing();
ts1.commit();//提交第一個事務
} catch(RunTimeException ex) {
ts1.rollback();//回滾第一個事務
} finally {
//釋放資源
}
}
- 3.TransactionDefinition.PROPAGATION_SUPPORTS:如果當前存在事務苏章,則加入該事務;如果當前沒有事務馍乙,則以非事務的方式繼續(xù)運行布近。所以說,并非所有的包在transactionTemplate.execute中的代碼都會有事務支持丝格。這個通常是用來處理那些并非原子性的非核心業(yè)務邏輯操作撑瞧。應用場景較少。
- 4.TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事務方式運行显蝌,如果當前存在事務预伺,則把當前事務掛起订咸。
- 5.TransactionDefinition.PROPAGATION_NEVER:要求上下文中不能存在事務,一旦有事務酬诀,就拋出runtime異常脏嚷,強制停止執(zhí)行!這個級別上輩子跟事務有仇瞒御。
- 6.TransactionDefinition.PROPAGATION_MANDATORY:該級別的事務要求上下文中必須要存在事務父叙,否則就會拋出異常!配置該方式的傳播級別是有效的控制上下文調(diào)用代碼遺漏添加事務控制的保證手段肴裙。比如一段代碼不能單獨被調(diào)用執(zhí)行趾唱,但是一旦被調(diào)用,就必須有事務包含的情況蜻懦,就可以使用這個傳播級別甜癞。
- 7.TransactionDefinition.PROPAGATION_NESTED:如果當前存在事務,則創(chuàng)建一個事務作為當前事務的嵌套事務來運行宛乃;如果當前沒有事務悠咱,則該取值等價于TransactionDefinition.PROPAGATION_REQUIRED。該傳播級別特征是征炼,如果上下文中存在事務析既,則嵌套事務執(zhí)行,如果不存在事務柒室,則新建事務渡贾。
那么什么是嵌套事務呢?
嵌套是子事務套在父事務中執(zhí)行雄右,子事務是父事務的一部分空骚,在進入子事務之前,父事務建立一個回滾點擂仍,叫save point囤屹,然后執(zhí)行子事務,這個子事務的執(zhí)行也算是父事務的一部分逢渔,然后子事務執(zhí)行結(jié)束肋坚,父事務繼續(xù)執(zhí)行。重點就在于那個save point肃廓≈茄幔看幾個問題就明了了:
如果子事務回滾,會發(fā)生什么盲赊?
父事務會回滾到進入子事務前建立的save point铣鹏,然后嘗試其他的事務或者其他的業(yè)務邏輯,父事務之前的操作不會受到影響哀蘑,更不會自動回滾诚卸。
如果父事務回滾葵第,會發(fā)生什么?
父事務回滾合溺,子事務也會跟著回滾卒密!為什么呢,因為父事務結(jié)束之前棠赛,子事務是不會提交的哮奇,我們說子事務是父事務的一部分,正是這個道理恭朗。那么:
事務的提交屏镊,是什么情況依疼?
是父事務先提交痰腮,然后子事務提交,還是子事務先提交律罢,父事務再提交膀值?答案是第二種情況,還是那句話误辑,子事務是父事務的一部分沧踏,由父事務統(tǒng)一提交。
現(xiàn)在你再體會一下這個”嵌套“巾钉,是不是有那么點意思翘狱?
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 {
//釋放資源
}
}
事務常用的兩個屬性:readonly和timeout
- readonly:設置事務為只讀以提升性能。
- timeout:設置事務的超時時間砰苍,一般用于防止大事務的發(fā)生潦匈。還是那句話,事務要盡可能的凶肌茬缩!
事務的回滾規(guī)則
通常情況下,如果在事務中拋出了未檢查異常(繼承自 RuntimeException 的異常)吼旧,則默認將回滾事務凰锡。如果沒有拋出任何異常,或者拋出了已檢查異常圈暗,則仍然提交事務掂为。這通常也是大多數(shù)開發(fā)者希望的處理方式,也是 EJB 中的默認處理方式员串。但是勇哗,我們可以根據(jù)需要人為控制事務在拋出某些未檢查異常時任然提交事務,或者在拋出某些已檢查異常時回滾事務昵济。
Spring 事務管理 API 分析
- TransactionDefinition:
它用于定義一個事務智绸。它包含了事務的靜態(tài)屬性野揪,比如:事務傳播行為、超時時間等等瞧栗。Spring 為我們提供了一個默認的實現(xiàn)類:DefaultTransactionDefinition斯稳,該類適用于大多數(shù)情況。如果該類不能滿足需求迹恐,可以通過實現(xiàn) TransactionDefinition 接口來實現(xiàn)自己的事務定義挣惰。 - PlatformTransactionManager:
用于執(zhí)行具體的事務操作.
根據(jù)底層所使用的不同的持久化 API 或框架,PlatformTransactionManager 的主要實現(xiàn)類大致如下:- DataSourceTransactionManager:適用于使用JDBC和iBatis進行數(shù)據(jù)持久化操作的情況殴边。
- HibernateTransactionManager:適用于使用Hibernate進行數(shù)據(jù)持久化操作的情況憎茂。
- JpaTransactionManager:適用于使用JPA進行數(shù)據(jù)持久化操作的情況。
另外還有JtaTransactionManager 锤岸、JdoTransactionManager竖幔、JmsTransactionManager等等。
- TransactionStatus
返回的TransactionStatus 對象可能代表一個新的或已經(jīng)存在的事務(如果在當前調(diào)用堆棧有一個符合條件的事務)是偷。TransactionStatus 接口提供了一個簡單的控制事務執(zhí)行和查詢事務狀態(tài)的方法拳氢。
Spring支持的事務管理類型
編程式事務管理:
這意味你通過編程的方式管理事務,給你帶來極大的靈活性蛋铆,但是難維護馋评。
用過 Hibernate 的人都知道,我們需要在代碼中顯式調(diào)用beginTransaction()刺啦、commit()留特、rollback()等事務管理相關的方法,這就是編程式事務管理玛瘸。通過 Spring 提供的事務管理 API蜕青,我們可以在代碼中靈活控制事務的執(zhí)行。在底層捧韵,Spring 仍然將事務操作委托給底層的持久化框架來執(zhí)行市咆。
- 基于底層 API 的編程式事務管理
根據(jù)PlatformTransactionManager、TransactionDefinition 和 TransactionStatus 三個核心接口再来,我們完全可以通過編程的方式來進行事務管理蒙兰。
public class BankServiceImpl implements BankService {
private BankDao bankDao;
private TransactionDefinition txDefinition;
private PlatformTransactionManager txManager;
public boolean transfer(Long fromId,Long toId芒篷,double amount) {
TransactionStatus txStatus = txManager.getTransaction(txDefinition);
boolean result = false;
try {
result = bankDao.transfer(fromId搜变,toId,amount);
txManager.commit(txStatus);
} catch(Exception e) {
result = false;
txManager.rollback(txStatus);
System.out.println("Transfer Error!");
}
return result;
}
}
//配置
<?xml version="1.0" encoding="utf-8"?>
<bean id="bankService" class="footmark.spring.core.tx.programmatic.origin.BankServiceImpl">
<property name="bankDao" ref="bankDao"/>
<property name="txManager" ref="transactionManager"/>
<property name="txDefinition">
<bean class="org.springframework.transaction.support.DefaultTransactionDefinition">
<property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/>
</bean>
</property>
</bean>
- 基于 TransactionTemplate 的編程式事務管理
Spring 在數(shù)據(jù)訪問層非常常見的模板回調(diào)模式针炉。
public class BankServiceImpl implements BankService {
private BankDao bankDao;
private TransactionTemplate transactionTemplate;
public boolean transfer(final Long fromId挠他,final Long toId,final double amount) {
return (Boolean) transactionTemplate.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
Object result;
try {
result = bankDao.transfer(fromId篡帕,toId殖侵,amount);
} catch(Exception e) {
status.setRollbackOnly();
result = false;
System.out.println("Transfer Error!");
}
return result;
}
});
}
}
//配置
<bean id="bankService"
class="footmark.spring.core.tx.programmatic.template.BankServiceImpl">
<property name="bankDao" ref="bankDao"/>
<property name="transactionTemplate" ref="transactionTemplate"/>
</bean>
聲明式事務管理:
這意味著你可以將業(yè)務代碼和事務管理分離贸呢,你只需用注解和XML配置來管理事務。
Spring 的聲明式事務管理在底層是建立在 AOP 的基礎之上的拢军。其本質(zhì)是對方法前后進行攔截楞陷,然后在目標方法開始之前創(chuàng)建或者加入一個事務,在執(zhí)行完目標方法之后根據(jù)執(zhí)行情況提交或者回滾事務茉唉。
聲明事務最細粒度只能作用到方法級別固蛾,無法做到像編程式事務那樣可以作用到代碼塊級別。
但其簡單度陆,這樣使得純業(yè)務代碼不被污染艾凯,極大方便后期的代碼維護。
- TransactionInterceptor 攔截器
<beans>
<bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="transfer">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<bean id="bankServiceTarget" class="footmark.spring.core.tx.declare.origin.BankServiceImpl">
<property name="bankDao" ref="bankDao"/>
</bean>
<bean id="bankService" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="bankServiceTarget"/>
<property name="interceptorNames">
<list>
<idref bean="transactionInterceptor"/>
</list>
</property>
</bean>
</beans>
首先懂傀,我們配置了一個 TransactionInterceptor 來定義相關的事務規(guī)則趾诗,他有兩個主要的屬性:一個是 transactionManager,用來指定一個事務管理器鸿竖,并將具體事務相關的操作委托給它沧竟;另一個是 Properties 類型的 transactionAttributes 屬性,它主要用來定義事務規(guī)則缚忧,該屬性的每一個鍵值對中,鍵指定的是方法名杈笔,方法名可以使用通配符闪水,而值就表示相應方法的所應用的事務屬性。
指定事務屬性的取值有較復雜的規(guī)則蒙具,這在 Spring 中算得上是一件讓人頭疼的事球榆。具體的書寫規(guī)則如下:
傳播行為 [,隔離級別] [禁筏,只讀屬性] [持钉,超時屬性] [不影響提交的異常] [,導致回滾的異常]
<property name="*Service">
PROPAGATION_REQUIRED篱昔,ISOLATION_READ_COMMITTED每强,TIMEOUT_20,+AbcException州刽,+DefException空执,-HijException
</property>
配置好了 TransactionInterceptor,我們還需要配置一個 ProxyFactoryBean 來組裝 target 和advice穗椅。這也是典型的 Spring AOP 的做法辨绊。通過 ProxyFactoryBean 生成的代理類就是織入了事務管理邏輯后的目標類。至此匹表,聲明式事務管理就算是實現(xiàn)了门坷。我們沒有對業(yè)務代碼進行任何操作宣鄙,所有設置均在配置文件中完成,這就是聲明式事務的最大優(yōu)點默蚌。
- TransactionProxyFactoryBean 代理
用于將TransactionInterceptor 和 ProxyFactoryBean 的配置合二為一
<beans>
<bean id="bankServiceTarget" class="footmark.spring.core.tx.declare.classic.BankServiceImpl">
<property name="bankDao" ref="bankDao"/>
</bean>
<bean id="bankService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="target" ref="bankServiceTarget"/>
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="transfer">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
</beans>
- 基于 <tx> 命名空間的聲明式事務管理
前面兩種聲明式事務配置方式奠定了 Spring 聲明式事務管理的基石框冀。在此基礎上,Spring 2.x 引入了 <tx> 命名空間敏簿,結(jié)合使用 <aop> 命名空間明也,帶給開發(fā)人員配置聲明式事務的全新體驗,配置變得更加簡單和靈活惯裕。另外温数,得益于 <aop> 命名空間的切點表達式支持,聲明式事務也變得更加強大蜻势。
<beans>
<bean id="bankService" class="footmark.spring.core.tx.declare.namespace.BankServiceImpl">
<property name="bankDao" ref="bankDao"/>
</bean>
<tx:advice id="bankAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="transfer" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="bankPointcut" expression="execution(* *.transfer(..))"/>
<aop:advisor advice-ref="bankAdvice" pointcut-ref="bankPointcut"/>
</aop:config>
</beans>
由于使用了切點表達式撑刺,我們就不需要針對每一個業(yè)務類創(chuàng)建一個代理對象了。
- 基于 @Transactional 的聲明式事務管理
除了基于命名空間的事務配置方式握玛,Spring 2.x 還引入了基于 Annotation 的方式够傍,具體主要涉及@Transactional 標注。@Transactional 可以作用于接口挠铲、接口方法冕屯、類以及類方法上。當作用于類上時拂苹,該類的所有 public 方法將都具有該類型的事務屬性安聘,同時,我們也可以在方法級別使用該標注來覆蓋類級別的定義瓢棒。
@Transactional(propagation = Propagation.REQUIRED)
public boolean transfer(Long fromId浴韭, Long toId, double amount) {
return bankDao.transfer(fromId脯宿, toId念颈, amount);
}
Spring 使用 BeanPostProcessor 來處理 Bean 中的標注,因此我們需要在配置文件中作如下聲明來激活該后處理 Bean
<tx:annotation-driven transaction-manager="transactionManager"/>
雖然 @Transactional 注解可以作用于接口连霉、接口方法榴芳、類以及類方法上,但是 Spring 小組建議不要在接口或者接口方法上使用該注解窘面,因為這只有在使用基于接口的代理時它才會生效翠语。另外, @Transactional 注解應該只被應用到 public 方法上财边,這是由 Spring AOP 的本質(zhì)決定的肌括。如果你在 protected、private 或者默認可見性的方法上使用 @Transactional 注解,這將被忽略谍夭,也不會拋出任何異常黑滴。
基于 <tx> 命名空間和基于 @Transactional 的事務聲明方式各有優(yōu)缺點〗羲鳎基于 <tx> 的方式袁辈,其優(yōu)點是與切點表達式結(jié)合,功能強大珠漂。利用切點表達式晚缩,一個配置可以匹配多個方法,而基于 @Transactional 的方式必須在每一個需要使用事務的方法或者類上用 @Transactional 標注媳危,盡管可能大多數(shù)事務的規(guī)則是一致的荞彼,但是對 @Transactional 而言,也無法重用待笑,必須逐個指定鸣皂。另一方面,基于 @Transactional 的方式使用起來非常簡單明了暮蹂,沒有學習成本寞缝。開發(fā)人員可以根據(jù)需要,任選其中一種使用仰泻,甚至也可以根據(jù)需要混合使用這兩種方式荆陆。
Spring框架的事務管理有哪些優(yōu)點
- 不同的事務API 提供一個統(tǒng)一的編程模式。
- 為編程式事務管理提供了一套簡單的API
- 支持聲明式事務管理我纪。
- 和Spring各種數(shù)據(jù)訪問抽象層很好得集成
你更傾向用那種事務管理類型慎宾?
- 選擇聲明式事務管理,因為它對應用代碼無侵入浅悉。
- 聲明式事務管理在可維護性上要優(yōu)于編程式事務管理,雖然比編程式事務管理(這種方式允許你通過代碼控制事務)少了一點靈活性券犁。
一個聲明式事務的實例
數(shù)據(jù)庫表
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
public interface BookShopDao {
// 根據(jù)書號獲取書的單價
public int findBookPriceByIsbn(String isbn);
// 更新書的庫存术健,使書號對應的庫存-1
public void updateBookStock(String isbn);
// 更新用戶的賬戶余額:account的balance-price
public void updateUserAccount(String username, int price);
}
//BookShopDaoImpl
@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
public interface BookShopService {
public void purchase(String username, String isbn);
}
//BookShopServiceImpl
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {
@Autowired
private BookShopDao bookShopDao;
/**
* 1.添加事務注解
* 使用propagation 指定事務的傳播行為溪窒,即當前的事務方法被另外一個事務方法調(diào)用時如何使用事務。
* 默認取值為REQUIRED,即使用調(diào)用方法的事務
* REQUIRES_NEW:使用自己的事務哪雕,調(diào)用的事務方法的事務被掛起。
*
* 2.使用isolation 指定事務的隔離級別,最常用的取值為READ_COMMITTED
* 3.默認情況下 Spring 的聲明式事務對所有的運行時異常進行回滾,也可以通過對應的屬性進行設置轴术。通常情況下,默認值即可钦无。
* 4.使用readOnly 指定事務是否為只讀逗栽。 表示這個事務只讀取數(shù)據(jù)但不更新數(shù)據(jù),這樣可以幫助數(shù)據(jù)庫引擎優(yōu)化事務失暂。若真的是一個只讀取數(shù)據(jù)庫值得方法彼宠,應設置readOnly=true
* 5.使用timeOut 指定強制回滾之前事務可以占用的時間。
*/
@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
public interface Cashier {
public void checkout(String username, List<String>isbns);
}
//CashierImpl:CashierImpl.checkout和bookShopService.purchase聯(lián)合測試了事務的傳播行為
@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
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
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
}
}