關(guān)于@Transactional事務(wù)嵌套使用方式
一、概述
事務(wù)(Transaction):指數(shù)據(jù)庫中執(zhí)行的一系列操作被視為一個邏輯單元,要么全部成功地執(zhí)行,要么全部失敗回滾,保證數(shù)據(jù)的一致性和完整性。
@Transactional注解是Spring框架提供的用于聲明事務(wù)的注解,作用于類和方法上。
1.1 @Transactional注解
屬性 | 可選值 | 作用 |
---|---|---|
propagation | REQUIREDREQUIRES_NEWNESTEDNOT_SUPPORTEDSUPPORTSMANDATORY | 指定事務(wù)的傳播行為,在事務(wù)嵌套時起作用,默認(rèn)值為Propagation.REQUIRED |
isolation | DEFAULT(和數(shù)據(jù)表一致)READ_UNCOMMITTED(讀-未提交)READ_COMMITTED(讀已提交)REPEATABLE_READ(可重復(fù)讀)SERIALIZABLE(串行化) | 指定事務(wù)的隔離級別,和數(shù)據(jù)庫的事務(wù)一致,默認(rèn)值為Isolation.DEFAULT |
readOnly | truefalse | 指定事務(wù)是否為只讀事務(wù),默認(rèn)值為false。如果將其設(shè)置為true,表示事務(wù)只涉及讀取操作 |
timeout | 數(shù)字(秒) | 指定事務(wù)的超時時間(秒),默認(rèn)值為TransactionDefinition.TIMEOUT_DEFAULT。如果事務(wù)在指定的時間內(nèi)未完成,將被自動回滾 |
rollbackFor | 異常類.Class | 指定觸發(fā)事務(wù)回滾的異常類型數(shù)組,默認(rèn)為空。當(dāng)方法拋出指定類型的異常時,事務(wù)將回滾 |
noRollbackFor | 異常類.Class | 指定不觸發(fā)事務(wù)回滾的異常類型數(shù)組,默認(rèn)為空。當(dāng)方法拋出指定類型的異常時,事務(wù)將不回滾 |
rollbackForClassName | 異常類名 | 與rollbackFor類似,但是使用異常類型的完全限定名字符串來指定觸發(fā)事務(wù)回滾的異常 |
noRollbackForClassName | 異常類名 | 與noRollbackFor類似,但是使用異常類型的完全限定名字符串來指定不觸發(fā)事務(wù)回滾的異常 |
1.2 Spring事務(wù)原理
Spring的事務(wù)是依靠aop實(shí)現(xiàn)的。
是在程序運(yùn)行時給代理對象創(chuàng)建代理類。
如果方法或類上添加了@Transcation注解,spring會自動給方法或類創(chuàng)建一個代理類,代理類中包含了開關(guān)事務(wù)的代碼和原始操作。
示意圖如下(原始類指加了@Transcation的類):
如果嵌套調(diào)用呢?
method1方法上有事務(wù)注解,method2沒有,method1調(diào)用了method2。
會生成如下的代理類:
二、@Transactional使用
2.1 事務(wù)失效的7種情況
這里用一個demo舉例子:更新一條數(shù)據(jù),我們先刪除數(shù)據(jù),再插入新數(shù)據(jù)(主鍵自動遞增)。
我們希望刪除或插入哪一方失敗,數(shù)據(jù)庫都能回滾。Spring事務(wù)失效是指發(fā)生異常依然不回滾。
1. 同一個類中方法調(diào)用
開發(fā)中避免不了會對同?個類??的?法調(diào)?,?如有?個類 Test,它的?個?法 A,A 再調(diào)?本類的?法 B(不論?法 B 是? public 還是 private 修飾),但?法 A 沒有聲明注解事務(wù),? B ?法有。
外部調(diào)??法 A 之后,?法 B 的事務(wù)是不會起作?的,這也是經(jīng)常犯錯誤的?個地?。
public class Test{ //外層 public void A(Category category) { this.categoryDao.delete(category); this.B(category);//無事務(wù),但有異常 } //內(nèi)層 @Transactional public void B(Category category) { this.categoryDao.insert(category); int a=2/0; } }
為什么失效:
其實(shí)這還是由于使? Spring AOP 代理造成的,沒加@Transactional注解的方法不會被代理類重寫,也就不會有事務(wù)。
2. 異常被 catch 住,而且沒有再次拋出異常
無論是外層異常還是內(nèi)層異常,只要捕獲以后沒有拋出異常,都不會回滾??偟膩碚f沒有異常不會回滾。
//外層 @Transactional public void updateById(Category category) { try { this.categoryDao.deleteById(category.getCid()); this.categoryDao.insert(category); int a=2/0; }catch (java.lang.Exception e){ System.out.println("updateById異常"); } }
//外層 public void update(Category category) { this.delete(category); this.categoryDao.insert(category); } //內(nèi)層 @Transactional void delete(Category category) { try{ this.categoryDao.delete(category.getId); int a=2/0; }catch(Exception e){ } }
解決辦法:
捕獲后再次拋出異常。無論是內(nèi)層、外層,只要重新拋出異常,就可以回滾。
//外層 public void update(Category category) { this.delete(category); this.categoryDao.insert(category); } //內(nèi)層 @Transactional void delete(Category category) { try{ this.categoryDao.delete(category.getId); int a=2/0; }catch(Exception e){ throw new RuntimeException("service層deleteById方法異常");//可以自定義異常 } }
結(jié)論:沒有異常不會回滾。
3. 拋出RuntimeException或Error以外的異常
@Transcation有個屬性(方法),管理何種異常會引發(fā)回滾。
默認(rèn)情況下,事務(wù)只在RuntimeException和Error上回滾。
拋出RuntimeException和Error以外類型的異常,不會回滾。
常見的非運(yùn)行時異常有:SQLException、IOException、FileNotFoundException、ReflectiveOperationException等等。
@Transactional void delete(Category category) throws SQLException{ this.categoryDao.delete(category.getId); int a=2/0; throw new SQLException("xxx"); //拋出異常也不會回滾 }
解決辦法:
使用RollbackFor屬 添加 要捕獲的異常類型,這樣除了RuntimeException和Error類型的異常,遇到Exception以及它的子類的異常,也會發(fā)生回滾。
@Transactional(rollbackFor = Exception.class) void delete(Category category) throws SQLException{ this.categoryDao.delete(category.getId); int a=2/0; throw new SQLException("xxx"); //這下就回滾了 }
結(jié)論:使用RollbackFor屬性添加要捕獲的異常類型
4. 子線程內(nèi)異常
刪除操作新開一個線程執(zhí)行,并且執(zhí)行中發(fā)生異常。結(jié)果是刪除回滾,插入沒回滾。
多線程環(huán)境下,內(nèi)外層是兩個事務(wù),事務(wù)具有隔離性,事務(wù)之間不會互相干擾。
//外層 @Transactional(rollbackFor = java.lang.Exception.class) public void update(Category category) { Thread thread=new Thread(new Runnable() { @Override public void run() { delete(category);//刪除 } }); this.categoryDao.insert(category);//插入 } //內(nèi)層 @Transactional(rollbackFor = java.lang.Exception.class) void delete(Category category) { this.categoryDao.delete(category.getId); int a=2/0; //異常 }
解決辦法:
使用Thread.UncaughtExceptionHandler接口捕獲線程異常,主線程發(fā)現(xiàn)了異常,也跟著回滾。
注:事務(wù)還是多個事務(wù)
1.創(chuàng)建一個實(shí)現(xiàn)了Thread.UncaughtExceptionHandler接口的異常處理器類,該類將負(fù)責(zé)捕獲未被捕獲的(沒加try-catch的)線程異常并進(jìn)行處理:
public class CustomUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { @Override public void uncaughtException(Thread t, Throwable e) { // 在此可以處理未被捕獲的線程異常 System.out.println("線程 " + t.getName() + " 發(fā)生了異常: " + e.getMessage()); } }
2.在主線程或創(chuàng)建的子線程中,設(shè)置自定義的異常處理器:
@Transactional(rollbackFor = java.lang.Exception.class) public void updateById(Category category){ Thread.setDefaultUncaughtExceptionHandler(new CustomUncaughtExceptionHandler());//加上這句 Thread thread=new Thread(new Runnable() { @Override public void run() { deleteById(category.getCid());//刪除 } }); thread.start(); this.categoryDao.insert(category);//插入 }
結(jié)論:子線程異常拋給主線程,兩者一起回滾。
5. 事務(wù)方法是private、static、final的
事務(wù)是依賴AOP實(shí)現(xiàn)的,如果方法不能被重寫,就不能生產(chǎn)代理類。
結(jié)論:java實(shí)現(xiàn)的動態(tài)代理的原理是代理類實(shí)現(xiàn)被代理類的相同接口、或成為被代理類的子類,然后重寫相同方法
6. 數(shù)據(jù)庫不支持事務(wù)
mysql的MyISAM存儲引擎是不支持事務(wù)的,它是舊版 MySQL(MySQL 5.5 之前)中的默認(rèn)存儲引擎。
7. 設(shè)置了某些事務(wù)傳播行為
在事務(wù)嵌套的時候,設(shè)置了以下傳播行為,會讓事務(wù)掛起(相當(dāng)于沒有事務(wù))或拋異常。
- TransactionDefinition.PROPAGATION_SUPPORTS:如果當(dāng)前存在事務(wù),則加?該事務(wù);如果當(dāng)前沒有事務(wù),則以?事務(wù)的?式繼續(xù)運(yùn)?。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以?事務(wù)?式運(yùn)?,如果當(dāng)前存在事務(wù),則把當(dāng)前事務(wù)掛起。
- TransactionDefinition.PROPAGATION_NEVER:以?事務(wù)?式運(yùn)?,如果當(dāng)前存在事務(wù),則拋出異常
//外層 @Transactional public void A(Category category) { this.categoryDao.insert(category); this.B(category.getCid()); } //內(nèi)層 @Transactional(propagation = Propagation.NOT_SUPPORTED) public boolean B(Integer cid) { int i = this.categoryDao.deleteById(cid); if(i>0) { throw new RuntimeException("xxx"); } return true; }
2.2 事務(wù)6種傳播機(jī)制
默認(rèn)是REQUIERD,保證只有一個事務(wù)。
總結(jié)
spring相當(dāng)多的功能都用到了動態(tài)代理,還需要對這方面知識做個總結(jié),學(xué)無止境啊。
相關(guān)文章
詳解DES加密算法的原理與Java實(shí)現(xiàn)
DES 加密,是對稱加密。對稱加密,顧名思義,加密和解密的運(yùn)算全都是使用的同樣的秘鑰。這篇文章主要為大家講講DES加密算法的原理與Java實(shí)現(xiàn),需要的可以參考一下2022-10-10JAVA生成八位不重復(fù)隨機(jī)數(shù)最快的方法總結(jié)(省時間省空間)
隨機(jī)數(shù)在實(shí)際中使用很廣泛,比如要隨即生成一個固定長度的字符串、數(shù)字,這篇文章主要給大家介紹了關(guān)于JAVA生成八位不重復(fù)隨機(jī)數(shù)最快的方法,文中介紹的方法省時間省空間,需要的朋友可以參考下2024-03-03MyBatis實(shí)現(xiàn)數(shù)據(jù)庫類型和Java類型的轉(zhuǎn)換
MyBatis 在處理數(shù)據(jù)庫查詢結(jié)果或傳遞參數(shù)時,需要將數(shù)據(jù)庫類型與 Java 類型之間進(jìn)行轉(zhuǎn)換,本文就給大家介紹MyBatis如何實(shí)現(xiàn)數(shù)據(jù)庫類型和 Java 類型的轉(zhuǎn)換的,需要的朋友可以參考下2024-09-09使用Spring Boot Maven插件的詳細(xì)方法
這篇文章主要介紹了如何使用Spring Boot Maven插件,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-05-05