關于@Transactional事務嵌套使用方式
一、概述
事務(Transaction):指數(shù)據(jù)庫中執(zhí)行的一系列操作被視為一個邏輯單元,要么全部成功地執(zhí)行,要么全部失敗回滾,保證數(shù)據(jù)的一致性和完整性。
@Transactional注解是Spring框架提供的用于聲明事務的注解,作用于類和方法上。
1.1 @Transactional注解
| 屬性 | 可選值 | 作用 |
|---|---|---|
| propagation | REQUIREDREQUIRES_NEWNESTEDNOT_SUPPORTEDSUPPORTSMANDATORY | 指定事務的傳播行為,在事務嵌套時起作用,默認值為Propagation.REQUIRED |
| isolation | DEFAULT(和數(shù)據(jù)表一致)READ_UNCOMMITTED(讀-未提交)READ_COMMITTED(讀已提交)REPEATABLE_READ(可重復讀)SERIALIZABLE(串行化) | 指定事務的隔離級別,和數(shù)據(jù)庫的事務一致,默認值為Isolation.DEFAULT |
| readOnly | truefalse | 指定事務是否為只讀事務,默認值為false。如果將其設置為true,表示事務只涉及讀取操作 |
| timeout | 數(shù)字(秒) | 指定事務的超時時間(秒),默認值為TransactionDefinition.TIMEOUT_DEFAULT。如果事務在指定的時間內(nèi)未完成,將被自動回滾 |
| rollbackFor | 異常類.Class | 指定觸發(fā)事務回滾的異常類型數(shù)組,默認為空。當方法拋出指定類型的異常時,事務將回滾 |
| noRollbackFor | 異常類.Class | 指定不觸發(fā)事務回滾的異常類型數(shù)組,默認為空。當方法拋出指定類型的異常時,事務將不回滾 |
| rollbackForClassName | 異常類名 | 與rollbackFor類似,但是使用異常類型的完全限定名字符串來指定觸發(fā)事務回滾的異常 |
| noRollbackForClassName | 異常類名 | 與noRollbackFor類似,但是使用異常類型的完全限定名字符串來指定不觸發(fā)事務回滾的異常 |
1.2 Spring事務原理
Spring的事務是依靠aop實現(xiàn)的。
是在程序運行時給代理對象創(chuàng)建代理類。
如果方法或類上添加了@Transcation注解,spring會自動給方法或類創(chuàng)建一個代理類,代理類中包含了開關事務的代碼和原始操作。
示意圖如下(原始類指加了@Transcation的類):
如果嵌套調(diào)用呢?
method1方法上有事務注解,method2沒有,method1調(diào)用了method2。
會生成如下的代理類:

二、@Transactional使用
2.1 事務失效的7種情況
這里用一個demo舉例子:更新一條數(shù)據(jù),我們先刪除數(shù)據(jù),再插入新數(shù)據(jù)(主鍵自動遞增)。
我們希望刪除或插入哪一方失敗,數(shù)據(jù)庫都能回滾。Spring事務失效是指發(fā)生異常依然不回滾。
1. 同一個類中方法調(diào)用
開發(fā)中避免不了會對同?個類??的?法調(diào)?,?如有?個類 Test,它的?個?法 A,A 再調(diào)?本類的?法 B(不論?法 B 是? public 還是 private 修飾),但?法 A 沒有聲明注解事務,? B ?法有。
外部調(diào)??法 A 之后,?法 B 的事務是不會起作?的,這也是經(jīng)常犯錯誤的?個地?。
public class Test{
//外層
public void A(Category category) {
this.categoryDao.delete(category);
this.B(category);//無事務,但有異常
}
//內(nèi)層
@Transactional
public void B(Category category) {
this.categoryDao.insert(category);
int a=2/0;
}
}為什么失效:
其實這還是由于使? Spring AOP 代理造成的,沒加@Transactional注解的方法不會被代理類重寫,也就不會有事務。

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方法異常");//可以自定義異常
}
}結論:沒有異常不會回滾。
3. 拋出RuntimeException或Error以外的異常
@Transcation有個屬性(方法),管理何種異常會引發(fā)回滾。
默認情況下,事務只在RuntimeException和Error上回滾。

拋出RuntimeException和Error以外類型的異常,不會回滾。
常見的非運行時異常有: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"); //這下就回滾了
}結論:使用RollbackFor屬性添加要捕獲的異常類型
4. 子線程內(nèi)異常
刪除操作新開一個線程執(zhí)行,并且執(zhí)行中發(fā)生異常。結果是刪除回滾,插入沒回滾。
多線程環(huán)境下,內(nèi)外層是兩個事務,事務具有隔離性,事務之間不會互相干擾。
//外層
@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)了異常,也跟著回滾。
注:事務還是多個事務
1.創(chuàng)建一個實現(xiàn)了Thread.UncaughtExceptionHandler接口的異常處理器類,該類將負責捕獲未被捕獲的(沒加try-catch的)線程異常并進行處理:
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)建的子線程中,設置自定義的異常處理器:
@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);//插入
}結論:子線程異常拋給主線程,兩者一起回滾。
5. 事務方法是private、static、final的
事務是依賴AOP實現(xiàn)的,如果方法不能被重寫,就不能生產(chǎn)代理類。
結論:java實現(xiàn)的動態(tài)代理的原理是代理類實現(xiàn)被代理類的相同接口、或成為被代理類的子類,然后重寫相同方法
6. 數(shù)據(jù)庫不支持事務
mysql的MyISAM存儲引擎是不支持事務的,它是舊版 MySQL(MySQL 5.5 之前)中的默認存儲引擎。
7. 設置了某些事務傳播行為
在事務嵌套的時候,設置了以下傳播行為,會讓事務掛起(相當于沒有事務)或拋異常。
- TransactionDefinition.PROPAGATION_SUPPORTS:如果當前存在事務,則加?該事務;如果當前沒有事務,則以?事務的?式繼續(xù)運?。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以?事務?式運?,如果當前存在事務,則把當前事務掛起。
- TransactionDefinition.PROPAGATION_NEVER:以?事務?式運?,如果當前存在事務,則拋出異常
//外層
@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 事務6種傳播機制
默認是REQUIERD,保證只有一個事務。

總結
spring相當多的功能都用到了動態(tài)代理,還需要對這方面知識做個總結,學無止境啊。
相關文章
JAVA生成八位不重復隨機數(shù)最快的方法總結(省時間省空間)
隨機數(shù)在實際中使用很廣泛,比如要隨即生成一個固定長度的字符串、數(shù)字,這篇文章主要給大家介紹了關于JAVA生成八位不重復隨機數(shù)最快的方法,文中介紹的方法省時間省空間,需要的朋友可以參考下2024-03-03
MyBatis實現(xiàn)數(shù)據(jù)庫類型和Java類型的轉換
MyBatis 在處理數(shù)據(jù)庫查詢結果或傳遞參數(shù)時,需要將數(shù)據(jù)庫類型與 Java 類型之間進行轉換,本文就給大家介紹MyBatis如何實現(xiàn)數(shù)據(jù)庫類型和 Java 類型的轉換的,需要的朋友可以參考下2024-09-09

