關(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實現(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;
}
}為什么失效:
其實這還是由于使? Spring AOP 代理造成的,沒加@Transactional注解的方法不會被代理類重寫,也就不會有事務(wù)。

2. 異常被 catch 住,而且沒有再次拋出異常
無論是外層異常還是內(nèi)層異常,只要捕獲以后沒有拋出異常,都不會回滾。總的來說沒有異常不會回滾。
//外層
@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)建一個實現(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實現(xiàn)的,如果方法不能被重寫,就不能生產(chǎn)代理類。
結(jié)論:java實現(xiàn)的動態(tài)代理的原理是代理類實現(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)文章
JAVA生成八位不重復(fù)隨機(jī)數(shù)最快的方法總結(jié)(省時間省空間)
隨機(jī)數(shù)在實際中使用很廣泛,比如要隨即生成一個固定長度的字符串、數(shù)字,這篇文章主要給大家介紹了關(guān)于JAVA生成八位不重復(fù)隨機(jī)數(shù)最快的方法,文中介紹的方法省時間省空間,需要的朋友可以參考下2024-03-03
MyBatis實現(xiàn)數(shù)據(jù)庫類型和Java類型的轉(zhuǎn)換
MyBatis 在處理數(shù)據(jù)庫查詢結(jié)果或傳遞參數(shù)時,需要將數(shù)據(jù)庫類型與 Java 類型之間進(jìn)行轉(zhuǎn)換,本文就給大家介紹MyBatis如何實現(xiàn)數(shù)據(jù)庫類型和 Java 類型的轉(zhuǎn)換的,需要的朋友可以參考下2024-09-09
使用Spring Boot Maven插件的詳細(xì)方法
這篇文章主要介紹了如何使用Spring Boot Maven插件,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-05-05

