Spring 的事務(wù)管理
事務(wù)原本是數(shù)據(jù)庫(kù)中的概念烘浦,在Dao層。但一般情況下填渠,需要將事務(wù)提升到業(yè)務(wù)層, 即Service層弦聂。這樣做是為了能夠使用事務(wù)的特性來(lái)管理具體的業(yè)務(wù)。
在 Spring 中通撤帐玻可以通過(guò)以下三種方式來(lái)實(shí)現(xiàn)對(duì)事務(wù)的管理:
(1)使用Spring的事務(wù)代理工廠管理事務(wù)
(2)使用Spring的事務(wù)注解管理事務(wù)
(3)使用AspectJ的AOP配置管理事務(wù)
- Spring 事務(wù)管理 API
Spring 的事務(wù)管理莺葫,主要用到兩個(gè)事務(wù)相關(guān)接口。
(1)事務(wù)管理器接口
事務(wù)管理器是 PlatformTransactionManager 接口對(duì)象枪眉。其主要用于完成事務(wù)的提交捺檬、回滾,及獲取事務(wù)的狀態(tài)信息贸铜。查看 SpringAPI 幫助文檔: Spring 框架解壓目錄下的
docs/javadoc-ap/index.html堡纬。
A聂受、常用的兩個(gè)實(shí)現(xiàn)類(lèi)
PlatformTransactionManager 接口有兩個(gè)常用的實(shí)現(xiàn)類(lèi): .
DataSoureTransactionManager:
使用 JDBC 或 iBatis 進(jìn)行持久化數(shù)據(jù)時(shí)使用。
HibernateTransactionManager:
使用 Hibernate 進(jìn)行持久化數(shù)據(jù)時(shí)使用烤镐。
B蛋济、Spring 的回滾方式
Spring 事務(wù)的默認(rèn)回滾方式是:發(fā)生運(yùn)行時(shí)異常時(shí)回滾,發(fā)生受查異常時(shí)提交炮叶。不過(guò)碗旅,對(duì)于受查異常,程序員也可以手工設(shè)置其回滾方式镜悉。
C祟辟、回顧錯(cuò)誤與異常
Throwable 類(lèi)是 Java 語(yǔ)言中所有錯(cuò)誤或異常的超類(lèi)。只有當(dāng)對(duì)象是此類(lèi)(或其子類(lèi)之一)的實(shí)例時(shí)侣肄,才能通過(guò) Java 虛擬機(jī)或者 Java 的 throw 語(yǔ)句拋出旧困。
Error 是程序在運(yùn)行過(guò)程中出現(xiàn)的無(wú)法處理的錯(cuò)誤,比如 OutOfMemoryError稼锅、
ThreadDeath吼具、NoSuchMethodError 等。當(dāng)這些錯(cuò)誤發(fā)生時(shí)缰贝,程序是無(wú)法處理( 捕獲或拋出)的馍悟,JVM一般會(huì)終止線(xiàn)程畔濒。
程序在編譯和運(yùn)行時(shí)出現(xiàn)的另一類(lèi)錯(cuò)誤稱(chēng)之為異常剩晴,它是 JVM 通知程序員的一種方式。通過(guò)這種方式侵状,讓程序員知道已經(jīng)或可能出現(xiàn)錯(cuò)誤赞弥,要求程序員對(duì)其進(jìn)行處理。
異常分為運(yùn)行時(shí)異常與受查異常趣兄。
運(yùn)行時(shí)異常绽左,是 RuntimeException 類(lèi)或其子類(lèi),即 只有在運(yùn)行時(shí)才出現(xiàn)的異常艇潭。如拼窥,NullPointerException、ArrayIndexOutOfBoundsException蹋凝、 llgalArgumentException 等均屬于運(yùn)行時(shí)異常鲁纠。這些異常由JVM拋出,在編譯時(shí)不要求必須處理( 捕獲或拋出)鳍寂。但改含,只要代碼編寫(xiě)足夠仔細(xì),程序足夠健壯迄汛,運(yùn)行時(shí)異常是可以避免的捍壤。
注意骤视,Hibernate 異常 HibernateException 就屬于運(yùn)行時(shí)異常。
受查異常鹃觉,也叫編譯時(shí)異常专酗,即在代碼編寫(xiě)時(shí)要求必須捕獲或拋出的異常,若不處理盗扇,則無(wú)法通過(guò)編譯笼裳。如SQLException, ClassNotFoundException, l0Exception等都屬于受查異常。
RuntimeException 及其子類(lèi)以外的異常,均屬于受查異常粱玲。當(dāng)然躬柬,用戶(hù)自定義的 Exception 的子類(lèi),即用戶(hù)白定義的異常也屬受查異常抽减。程序員在定義異常時(shí)允青,只要未明確聲明定義的為 RuntimeException 的子類(lèi),那么定義的就是受查異常卵沉。
(2)事務(wù)定義接口
事務(wù)定義接口 TransactionDefinition 中定義了事務(wù)描述相關(guān)的三類(lèi)常量:事務(wù)隔離級(jí)別颠锉、事務(wù)傳播行為、事務(wù)默認(rèn)超時(shí)時(shí)限史汗,及對(duì)它們的操作琼掠。
A、定義了五個(gè)事務(wù)隔離級(jí)別常量
這些常量均是以 ISOLATION_開(kāi)頭停撞。即形如ISOLATION_ XXX瓷蛙。
DEFAULT:
采用DB默認(rèn)的事務(wù)隔離級(jí)別。MySql 的默認(rèn)為REPEATABLE_ READ; Oracle 默認(rèn)為 READ_ COMMITTED戈毒。
READ_ _UNCOMMITTED:
讀未提交艰猬。未解決任何并發(fā)問(wèn)題。
READ_ COMMITTED:
讀已提交埋市。解決臟讀冠桃,存在不可復(fù)讀與幻讀。
REPEATABLE_ READ:
可重復(fù)讀道宅。"解決臟讀食听、不可重復(fù)讀,存在幻讀
SERIALIZABLE:
串行化污茵。不存在并發(fā)問(wèn)題樱报。
B、定義了七個(gè)事務(wù)傳播行為常量
所謂事務(wù)傳播行為是指省咨,處于不同事務(wù)中的方法在相互調(diào)用時(shí)肃弟,執(zhí)行期間事務(wù)的維護(hù)情況。如,A 事務(wù)中的方法笤受,doSome()調(diào)用 B 事務(wù)中的方法 doOther()穷缤,在調(diào)用執(zhí)行期間事務(wù)的維護(hù)情況,就稱(chēng)為事務(wù)傳播行為箩兽。事務(wù)傳播行為是加在方法上的津肛。
事務(wù)傳播行為常量都是以 PROPAGATION_ 開(kāi)頭, 形如 PROPAGATION_XXX汗贫。
a身坐、REQUIRED:
指定的方法必須在事務(wù)內(nèi)執(zhí)行。若當(dāng)前存在事務(wù)落包,就加入到當(dāng)前事務(wù)中;若當(dāng)前沒(méi)有事務(wù)部蛇,則創(chuàng)建一個(gè)新事務(wù)。這種傳播行為是最常見(jiàn)的選擇咐蝇,也是 Spring 默認(rèn)的事務(wù)傳播行為涯鲁。
如該傳播行為加在 doOther() 方法上。若 doSome() 方法在調(diào)用 doOther() 方法時(shí)就是在事務(wù)內(nèi)運(yùn)行的有序,則 doOther() 方法的執(zhí)行也加入到該事務(wù)內(nèi)執(zhí)行抹腿。若 doSome() 方 法在調(diào)用 doOther() 方法時(shí)沒(méi)有在事務(wù)內(nèi)執(zhí)行,則doOther()方法會(huì)創(chuàng)建一個(gè)事務(wù)旭寿,并在其中執(zhí)行警绩。
b、SUPPORTS
指定的方法支持當(dāng)前事務(wù)盅称,但若當(dāng)前沒(méi)有事務(wù)肩祥,也可以以非事務(wù)方式執(zhí)行。
c微渠、MANDATORY
指定的方法必須在當(dāng)前事務(wù)內(nèi)執(zhí)行搭幻,若當(dāng)前沒(méi)有事務(wù),則直接拋出異常逞盆。.
d、REQUIRES_NEW
總是新建一個(gè)事務(wù)松申,若當(dāng)前存在事務(wù)云芦,就將當(dāng)前事務(wù)掛起,直到新事務(wù)執(zhí)行完畢贸桶。
e舅逸、NOT_SUPPORTED
指定的方法不能在事務(wù)環(huán)境中執(zhí)行,若當(dāng)前存在事務(wù)皇筛,就將當(dāng)前事務(wù)掛起琉历。
f、NEVER
指定的方法不能在事務(wù)環(huán)境下執(zhí)行,若當(dāng)前存在事務(wù)旗笔,就直接拋出異常彪置。
g、NESTED
指定的方法必須在事務(wù)內(nèi)執(zhí)行蝇恶。若當(dāng)前存在事務(wù)拳魁,則在嵌套事務(wù)內(nèi)執(zhí)行;若當(dāng)前沒(méi)有事務(wù)撮弧,則創(chuàng)建一個(gè)新事務(wù)潘懊。
C、定義了默認(rèn)事務(wù)超時(shí)時(shí)限
常量 TIMEOUT_DEFAULT 定義了事務(wù)底層默認(rèn)的超時(shí)時(shí)限贿衍,及不支持事務(wù)超時(shí)時(shí)限設(shè)置的 none 值授舟。
注意,事務(wù)的超時(shí)時(shí)限起作用的條件比較多贸辈,且超時(shí)的時(shí)間計(jì)算點(diǎn)較復(fù)雜岂却。所以,該值一般就使用默認(rèn)值即可裙椭。
- 程序舉例環(huán)境搭建
舉例:購(gòu)買(mǎi)股票 Spring_Transaction_Buystock 項(xiàng)目
本例要實(shí)現(xiàn)模擬購(gòu)實(shí)股票躏哩。存在兩個(gè)實(shí)體:銀行賬戶(hù) Account 與股票賬戶(hù) Stock。當(dāng)要購(gòu)買(mǎi)股票時(shí)揉燃,需要從 Account 中扣除相應(yīng)金額的存款扫尺,然后在 Stock 中增加相應(yīng)的股票數(shù)量。而在這個(gè)過(guò)程中炊汤,可能會(huì)拋出一個(gè)用戶(hù)自定義的異常正驻。異常的拋出,將會(huì)使兩個(gè)操作回滾抢腐。實(shí)現(xiàn)步驟:
Step1:
創(chuàng)建數(shù)據(jù)庫(kù)表
創(chuàng)建兩個(gè)數(shù)據(jù)庫(kù)表 account姑曙、stock。
Step2:
創(chuàng)建實(shí)體類(lèi)
創(chuàng)建兩個(gè)實(shí)體類(lèi) Account 與 Stock迈倍。
Step3:
定義 Dao 接口
定義兩個(gè) Dao 接口 IAccountDao 與 IStockDao伤靠。
Step4:
定義 Dao 實(shí)現(xiàn)類(lèi)
定義兩個(gè) dao 接口的實(shí)現(xiàn)類(lèi) AccountDaolmpl 與 StockDaolmpl,注意啼染,它們要繼承自 JdbcDaoSupport 宴合。
Step5:
定義異常類(lèi)
定義 service 層可能會(huì)拋出的異常類(lèi) StockException。
Step6:
定義 Service 接口
定義 Service 接口 IStockProcessService 迹鹅。
Step7:
定義 Service 的實(shí)現(xiàn)類(lèi)
定義 Service 層接口的實(shí)現(xiàn)類(lèi) StockProcessServiceImpl 卦洽。
Step8:
Spring 配置文件中添加最全約束
本例中將使用到 Spring 中DI、AOP斜棚、事務(wù)等眾多功能阀蒂,所以將之前用過(guò)的所有約束進(jìn)行了綜合该窗。綜合后的約束為:
Step9:
修改 Spring 配置文件內(nèi)容
Step10:
定義測(cè)試類(lèi)
定義 view 層測(cè)試類(lèi) MyTest。現(xiàn)在就可以在無(wú)事務(wù)代理的情況下運(yùn)行了蚤霞。
- 使用 Spring 的事務(wù)代理工廠管理事務(wù)
該方式是酗失,需要為目標(biāo)類(lèi),即 Service 的實(shí)現(xiàn)類(lèi)創(chuàng)建事務(wù)代理争便。事務(wù)代理使用的類(lèi)是 TransactionProxyFactoryBean级零,該類(lèi)需要初始化如下一些屬性:
transactionManager:
事務(wù)管理器
target:
目 標(biāo)對(duì)象,即Service 實(shí)現(xiàn)類(lèi)對(duì)象
transactionAttributes:
事務(wù)屬性設(shè)置
對(duì)于 XML 配置代理方式實(shí)現(xiàn)事務(wù)管理時(shí)滞乙,受查異常的回滾方式奏纪,程序員可以通過(guò)以下方式進(jìn)行設(shè)置:通過(guò)“異常"方式,可使發(fā)生指定的異常時(shí)事務(wù)回滾;通過(guò)“+異痴镀簦”方式序调,可使發(fā)生指定的異常時(shí)事務(wù)提交。
該方式的實(shí)現(xiàn)步驟為:
Step1:
復(fù)制項(xiàng)目
復(fù)制 Spring_Transaction_Buystock 項(xiàng)目兔簇,并重命名為Spring_Transaction_Proxy发绢。 在此基礎(chǔ)上修改。
Step2:
導(dǎo)入 Jar 包
這里使用到的 Spring 的 AOP垄琐,所以需要引入 AOP 的兩個(gè) Jar 包:aop 聯(lián)盟边酒,及 Spring 對(duì) AOP 實(shí)現(xiàn)的 Jar 包:
Step3:
在容器中添加事務(wù)管理器DataSourceTransactionManager
由于本項(xiàng)目使用的是 JDBC 進(jìn)行持久化,所以使用 DataSourceTransactionManager 類(lèi)作為事務(wù)管理器狸窘。
Step4:
在容器中添加事務(wù)代理TransactionProxyFactoryBean
Step5:
修改測(cè)試類(lèi)
現(xiàn)在就可以通過(guò)事務(wù)代理來(lái)運(yùn)行了墩朦。
- 使用 Spring 的事務(wù)注解管理事務(wù)
通過(guò) @Transactiona l注解方式,也可將事務(wù)織入到相應(yīng)方法中翻擒。而使用注解方式氓涣,只需在配置文件中加入一個(gè) tx 標(biāo)簽,以告訴 spring 使用注解來(lái)完成事務(wù)的織入陋气。該標(biāo)簽只需指定一個(gè)屬性劳吠,事務(wù)管理器。
@Transactional 的所有可選屬性如下所示:
propagation:
用于設(shè)置事務(wù)傳播屬性巩趁。該屬性類(lèi)型為 Propagation 枚舉痒玩,默認(rèn)值為
Propagation.REQUIRED。
isolation:
用于設(shè)置事務(wù)的隔離級(jí)別晶渠。該屬性類(lèi)型為Isolation 枚舉凰荚,默認(rèn)值為
Isolation.DEFAULT。
readOnly:
用于設(shè)置該方法對(duì)數(shù)據(jù)庫(kù)的操作是否是只讀的。該屬性為 boolean, 默認(rèn)值為 false第租。
timeout:
用于設(shè)置本操作與數(shù)據(jù)庫(kù)連接的超時(shí)時(shí)限盛卡。單位為秒,類(lèi)型為 int船庇,默認(rèn)值為-1蒿赢,即沒(méi)有時(shí)限涤妒。
rollbackFor:
指定需要回滾的異常類(lèi)颁督。類(lèi)型為 Class[]践啄, 默認(rèn)值為空數(shù)組。當(dāng)然沉御,若只有一個(gè)異常類(lèi)時(shí)屿讽,可以不使用數(shù)組。
rollbackForClassName:
指定需要回滾的異常類(lèi)類(lèi)名吠裆。類(lèi)型為String[],默認(rèn)值為空數(shù)組伐谈。當(dāng)然,若只有一個(gè)異常類(lèi)時(shí)试疙,可以不使用數(shù)組诵棵。.
noRollbackFor:
指定不需要回滾的異常類(lèi)。類(lèi)型為 Class[]祝旷, 默認(rèn)值為空數(shù)組履澳。當(dāng)然,若只有一一個(gè)異常類(lèi)時(shí)怀跛,可以不使用數(shù)組距贷。
noRollbackForClassName:
指定不需要回滾的異常類(lèi)類(lèi)名。類(lèi)型為 String[]吻谋, 默認(rèn)值為空數(shù)組忠蝗。當(dāng)然,若只有一個(gè)異常類(lèi)時(shí)滨溉,可以不使用數(shù)組什湘。
需要注意的是,@Transactional若用在方法上,只能用于public方法上晦攒。對(duì)于其他非 public 方法闽撤,如果加_上了注解 @Transactional,雖然 Spring 不會(huì)報(bào)錯(cuò)脯颜,但不會(huì)將指定事務(wù)織入到該方法中哟旗。因?yàn)?Spring 會(huì)忽略掉所有非 public 方法上的 @Transaction注解。
若 @Transaction 注解在類(lèi)上栋操,則表示該類(lèi)上所有的方法均將在執(zhí)行時(shí)織入事務(wù)闸餐。
Step1:
復(fù)制項(xiàng)目
復(fù)制 Spring_Transaction_Buystock 項(xiàng)目,并重命名為Spring_Transaction_Annotation矾芙。 在此基礎(chǔ)上修改舍沙。
Step2:
在容器中添加事務(wù)管理器
Step3:
在 Service 實(shí)現(xiàn)類(lèi)方法上添加注解
Step4:
修改配置文件內(nèi)容
Step4:
修改測(cè)試類(lèi)
由于配置文件中已不存在事務(wù)代理對(duì)象,所以測(cè)試類(lèi)中要從容器中獲取的將不再是事務(wù)代理對(duì)象剔宪,而是原來(lái)的目標(biāo)對(duì)象拂铡。
- 使用 AspectJ 的 AOP 配置管理事務(wù)(重點(diǎn))
使用 XML 配置事務(wù)代理的方式的不足是壹无,每個(gè)目標(biāo)類(lèi)都需要配置事務(wù)代理。當(dāng)目標(biāo)類(lèi)較多感帅,配置文件會(huì)變得非常臃腫斗锭。
使用 XML 配置顧問(wèn)方式可以自動(dòng)為每個(gè)符合切入點(diǎn)表達(dá)式的類(lèi)生成事務(wù)代理。其用法很簡(jiǎn)單失球,只需將前面代碼中關(guān)于事務(wù)代理的配置刪除岖是,再替換為如下內(nèi)容即可。
Step1:
復(fù)制項(xiàng)目
復(fù)制 Spring_Transaction_Buystock 項(xiàng)目实苞,并重命名為Spring_Transaction_Advisor豺撑。 在此基礎(chǔ)上修改。
Step2:
導(dǎo)入 Jar 包
這里使用 Spring 的 AspecJ 方式將事務(wù)進(jìn)行的織入硬梁,所以前硫,這里除了前面導(dǎo)入的 aop 的兩個(gè) Jar 包外,還需要兩個(gè)Jar包:AspectJ 的 Jar 包荧止,及 Spring 整合 AspectJ 的 Jar 包屹电。
Step3:
在容器中添加事務(wù)管理器
Step4:
配置事務(wù)通知
為事務(wù)通知設(shè)置相關(guān)屬性。用于指定要將事務(wù)以什么方式織入給哪些方法跃巡。
例如危号,應(yīng)用到 buyStock 方法上的事務(wù)要求是必須的,且當(dāng)buyStock 方法發(fā)生 StockException 后素邪,要回滾外莲。
Step5:
配置顧問(wèn)
注意,不能寫(xiě)為下面的形式兔朦,切入點(diǎn)表達(dá)式一定要指明切入點(diǎn)在 Service 層偷线,否則將會(huì)拋出對(duì)數(shù)據(jù)源的循環(huán)引用異常。因?yàn)橄旅娴膶?xiě)法同時(shí)會(huì)把 Service 層與 Dao 層的方法均作為切入點(diǎn)沽甥,Service 與 Dao 中均注入了數(shù)據(jù)源声邦,而 Service 又調(diào)用了 Dao,所以就出現(xiàn)了循環(huán)調(diào)用的異常摆舟。
Step6:
修改測(cè)試類(lèi)
測(cè)試類(lèi)中要從容器中獲取的將不再是事務(wù)代理對(duì)象亥曹,而是目標(biāo)對(duì)象。