一甜滨、Transaction:
Spring中提供了很好的事務(wù)管理機制--編程式事務(wù)和聲明式事務(wù)衣摩。編程式事務(wù)管理是侵入性事務(wù)管理昭娩,使用TransactionTemplate或者PlatformTransactionManager手動管理事務(wù)的提交栏渺、回滾等操作锐涯。其中聲明式事務(wù)建立在AOP之上纹腌,原理是對方法進行攔截,在目標方法執(zhí)行之前添加事務(wù)莱褒,目標方法執(zhí)行后根據(jù)執(zhí)行情況進行事務(wù)的提交或回滾广凸。編程式事務(wù)是侵入式的蛛枚,聲明式事務(wù)是非侵入性的,因此扭吁,提倡使用聲明式事務(wù)侥袜。@Transactional注解是實現(xiàn)聲明式事務(wù)的方式之一系馆。
1.1作用域
@Transaction 可以作用在類由蘑、接口代兵、方法上
類上:表示給該類所有的 public 方法添加上 @Transaction 注解
接口上:只有在使用基于接口的代理時它才會生效植影。像 CGLib 動態(tài)代理采用繼承的方式將會導(dǎo)致 @Transactional 注解失效思币。不推薦使用
方法上:事務(wù)的作用域就只在該方法上生效,并且如果類及方法上都配置 @Transaction 注解時惶我,方法的注解會覆蓋類上的注解
1.2屬性
屬性說明
1)valuevalue 主要用來指定不同的事務(wù)管理器,滿足在同一個系統(tǒng)中听怕,存在不同的事務(wù)管理器虑绵。如果在 Spring 中翅睛,配置了多個數(shù)據(jù)源聲明了多個事務(wù)管理器宏所,可以通過該參數(shù)來進行指定事務(wù)管理器爬骤。
2)propagation
枚舉值含義
3)isolation
4)rollbackFor 和 rollbackForClassNamerollbackFor:通過類進行指定骤铃,如@Transactional(rollbackFor = {Exception.class})
rollbackForClassName:通過類名進行指定,如@Transaction(rollbackForClassName = {"java.lang.Exception"})
5)noRollbackFor 和 noRollbackForClassName與4)一致
6)手動回滾TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
二喊暖、總覽:
三陵叽、失效場景
3.1丛版、代理異常導(dǎo)致
Spring中對注解解析的都是基于代理的页畦,如果目標方法無法被Spring代理到豫缨,那么它將無法被Spring進行事務(wù)管理。
Spring生成代理的方式有兩種:
(1)基于接口的JDK動態(tài)代理燃箭,要求目標代理類需要實現(xiàn)一個接口才能被代理
(2)基于實現(xiàn)目標類子類的CGLIB代理Spring在2.0之前遍膜,目標類如果實現(xiàn)了接口瓢颅,則使用JDK動態(tài)代理方式挽懦,否則通過CGLIB子類的方式生成代理信柿。而在2.0版本之后渔嚷,如果不在配置文件中顯示的指定spring.aop.proxy-tartget-class的值稠曼,默認情況下生成代理的方式為CGLIB形病,如下圖
3.1.1 將注解標注在接口方法上
@Transactional是支持標注在方法與類上的。一旦標注在接口上,對應(yīng)接口實現(xiàn)類的代理方式如果是CGLIB漠吻,將通過生成子類的方式生成目標類的代理量瓜,將無法解析到@Transactional,從而事務(wù)失效途乃。
3.1.2 被final绍傲、static關(guān)鍵字修飾的類或方法
事務(wù)的管理是通過代理執(zhí)行的方式生效的,如果是方法內(nèi)部調(diào)用耍共,將不會走代理邏輯烫饼,也就調(diào)用不到了。
3.1.3 類方法內(nèi)部調(diào)用
事務(wù)的管理是通過代理執(zhí)行的方式生效的划提,如果是方法內(nèi)部調(diào)用伊履,將不會走代理邏輯,也就調(diào)用不到哄辣。
解決方式:方式1:新建一個Service,將方法遷移過去崖面,使被調(diào)用方獨立出去甲棍。
方式2:在當前類注入自己,調(diào)用createUser1時通過注入的userService調(diào)用
方式3:通過AopContext.currentProxy()獲取代理對象
3.1.4 未被Spring管理
沒有被Spring管理成為IOC容器中的一個bean呈驶,無法被事務(wù)切面代理到聋迎。
3.2 框架不支持
3.2.1 非public修飾方法
3.2.2 多線程調(diào)用
在多線程/異步線程的使用場景颅围,也會導(dǎo)致事務(wù)失效的發(fā)生;TransactionAspectSupport.prepareTransactionInfo方法中渐溶,能夠看到在完成事務(wù)的狀態(tài)配置后會綁定到當前的線程眉睹,所以異步線程/多線程的使用會導(dǎo)致事務(wù)傳播的失效慕蔚。
3.2.3 數(shù)據(jù)庫本身不支持事務(wù)
比如Mysql的Myisam存儲引擎是不支持事務(wù)的桂对,只有innodb存儲引擎才支持宅此。
3.3 配置錯誤
3.3.1 傳播機制配置錯誤
不支持事務(wù)的傳播機制為:PROPAGATION_SUPPORTS璧亮,PROPAGATION_NOT_SUPPORTED,PROPAGATION_NEVER搭儒。
如果配置了以上三種傳播方式的話铃岔,在發(fā)生異常的時候,事務(wù)是不會回滾的稍浆。
3.3.2 rollbackFor屬性設(shè)置錯誤
默認情況下事務(wù)僅回滾運行時異常(RuntimeException)和Error朗伶,不回滾受檢異常(例如IOException)纯丸。所以默認配置可以保證RuntimeException錯誤的回滾队橙,如果想保證非RuntimeException錯誤的回滾贮匕,需要加上rollbackFor = Exception.class參數(shù),或者其他指定的異常類型生均。
因此如果方法中拋出了IO異常录别,默認情況下事務(wù)也會回滾失敗。
我們可以通過指定@Transactional(rollbackFor = Exception.class)的方式進行全異常捕獲赵讯。
(1)受檢異常:在編譯的時候鱼响,要強制檢查的異常丈积,這種異常需要去顯示的通過try/catch來進行捕獲或者通過throws去拋出去否則程序無法通過編譯的;
(2)非受檢異常:非受檢異常表示編譯器可以不需要去強制去檢查異常唬滑,這種異常不需要去顯示去捕獲或者拋出
3.4 使用錯誤
3.4.1 異常被內(nèi)部catch
默認情況下標注了@Transactional注解的方法的事務(wù)傳播機制是REQUIRED,它的特性是支持當前事務(wù),也就說加入當前事務(wù)。我們在UserService中開始事務(wù),然后再UserService1中拋出異惩炊猓回滾UserService中的事務(wù)匙头,將其標記為只讀漫谷。
但是在UserSevice中我們捕獲了異常,此時UserService上的事務(wù)認為正常提交事務(wù)乾胶。最后在提交時發(fā)現(xiàn)事務(wù)只讀抖剿,已經(jīng)被回滾,則拋出了上述異常识窿。
因此這里如果需要對特定的異常進行捕獲處理斩郎,記得再次將異常拋出,讓最外層的事務(wù)感知到喻频。
3.4.2 嵌套事務(wù)
嵌套事務(wù)失效主要是在兩個或者多個事務(wù)嵌套在一起時缩宜,且內(nèi)外層對事務(wù)rollbackFor參數(shù)配置不一致的場景;(1)外層加上了rollbackFor = Exception.class而內(nèi)層使用默認甥温,無論內(nèi)層還是外層拋出非RuntimeException錯誤均能夠正扯突停回滾!(2)若內(nèi)層加上了rollbackFor = Exception.class而外層使用默認姻蚓,若外層拋出非RuntimeException錯誤是無法正常的回滾宋梧。若內(nèi)層拋出非RuntimeException錯誤,則都能正痴玻回滾捂龄。主要是因為在使用默認propagation配置時是使用的propagationPropagation.REQUIRED;REQUIRED的意思是說,事務(wù)嵌套的時候加叁,如果發(fā)現(xiàn)已經(jīng)有事務(wù)存在了倦沧,就加入這個事務(wù),而不是新建一個事務(wù)它匕,所以根本就不存在兩個事務(wù)展融,一直只有一個;當外層報錯的時候豫柬,此時查看事務(wù)告希,沒有增加處理非RuntimeException能力扑浸,所以代碼走完,貌似“兩個事務(wù)”暂雹,都回滾失敗了首装。當里面報錯的時候,事務(wù)已經(jīng)添加上了處理非RuntimeException能力杭跪,所以仙逻,代碼走完就回滾成功了