Spring事務(wù)@Transactional失效的8大場景與解決方法
1. 前言
在我們開發(fā)Spring Boot應(yīng)用中,很多小伙伴以為只要在方法上加一個 @Transactional,事務(wù)就能自動回滾,保證數(shù)據(jù)一致性。但實際開發(fā)中,事務(wù)經(jīng)常出現(xiàn)失效的情況:明明拋了異常,數(shù)據(jù)庫還是提交了。你肯定會疑惑:“為什么我加了注解,數(shù)據(jù)還是沒回滾?”

通過本文博主將徹底和小伙伴們說清楚,讓大家別再踩坑!從 @Transactional 的參數(shù)詳解入手,再結(jié)合常見事務(wù)失效場景給出 正確寫法 vs 錯誤寫法 對比,幫助小伙伴們徹底理解 Spring 事務(wù)機制。
2. @Transactional 核心參數(shù)解析
Spring的 @Transactional 提供了很多參數(shù),但日常最常用的主要有以下幾個:
@Transactional(
propagation = Propagation.REQUIRED, // 事務(wù)傳播行為
isolation = Isolation.DEFAULT, // 事務(wù)隔離級別
rollbackFor = Exception.class, // 回滾規(guī)則
timeout = 30, // 超時時間(秒)
readOnly = false // 是否只讀事務(wù)
)
2.1 propagation (事務(wù)傳播行為)
作用:定義了當(dāng)前事務(wù)方法與另一個事務(wù)方法相互調(diào)用時,事務(wù)應(yīng)該如何傳播。
Spring的傳播屬性有以下幾種:
REQUIRED (默認):如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前沒有事務(wù),則創(chuàng)建一個新的事務(wù)。
假設(shè)現(xiàn)在有一個需求添加用戶的時候給用戶同時發(fā)放優(yōu)惠券
用戶Service中userService.saveUser() 方法 調(diào)用 優(yōu)惠券Service的couponService.add()方法。
當(dāng)userService.saveUser() 運行時,先開啟一個事務(wù),此時couponService.add()方法發(fā)現(xiàn)已經(jīng)存在一個事務(wù),就不會再開啟事務(wù),只要任何一個方法報錯則都會回調(diào)。
REQUIRES_NEW:創(chuàng)建一個新的事務(wù),如果當(dāng)前存在事務(wù),則把當(dāng)前事務(wù)掛起。
依舊以上述添加用戶的時候給用戶同時發(fā)放優(yōu)惠券為例子
當(dāng)userService.saveUser()運行時,先開啟一個事務(wù)A。當(dāng)運行couponService.add()時,把事務(wù)A掛起,然后開啟事務(wù)B。就算事務(wù)A發(fā)生回滾,事務(wù)B依然能正常提交。
外部事務(wù)不會影響內(nèi)部事務(wù)的提交和回滾。
- SUPPORTS:如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前沒有事務(wù),則以非事務(wù)方式繼續(xù)運行。
- NOT_SUPPORTED:以非事務(wù)方式運行,如果當(dāng)前存在事務(wù),則把當(dāng)前事務(wù)掛起。
- MANDATORY:必須在一個已有的事務(wù)中運行,否則拋出異常。
- NEVER:必須在非事務(wù)方式下運行,如果當(dāng)前存在事務(wù),則拋出異常。
- NESTED:如果當(dāng)前存在事務(wù),則在嵌套事務(wù)內(nèi)執(zhí)行;如果當(dāng)前沒有事務(wù),則行為同 REQUIRED。
2.2 isolation(隔離級別)
作用:定義事務(wù)的隔離級別,解決并發(fā)事務(wù)可能帶來的臟讀、不可重復(fù)讀、幻讀等問題。
- READ_COMMITTED:避免臟讀,但可能有不可重復(fù)讀。
- REPEATABLE_READ:避免臟讀和不可重復(fù)讀。
- SERIALIZABLE:最高級別,完全串行化,性能差。
2.3 rollbackFor / noRollbackFor(回滾規(guī)則)
作用:指定哪些異常會觸發(fā)事務(wù)回滾
默認:僅遇到運行時異常 RuntimeException 和 Error 才回滾
如果要對檢查型異常(如 IOException)回滾,必須顯式配置 rollbackFor = Exception.class
2.4 timeout (超時時間)
作用:事務(wù)的超時時間,單位為秒。如果超過該時間事務(wù)尚未完成,則自動回滾
2.5 readOnly(是否只讀事務(wù))
作用:提示數(shù)據(jù)庫該事務(wù)為只讀事務(wù),數(shù)據(jù)庫可能會進行一些優(yōu)化。默認為false
3. 事務(wù)失效的常見場景
下面列出最常見的 8大事務(wù)失效場景,并配合代碼示例對比
3.1 方法不是 public
Spring 默認使用基于代理的 AOP,對于非 public 方法,代理對象無法攔截到其調(diào)用,導(dǎo)致注解失效。
錯誤寫法:
@Service
public class UserService {
@Transactional
void saveUser(User user) { // 不是public
userMapper.insert(user);
throw new RuntimeException("模擬異常");
}
}
正確寫法:
@Service
public class UserService {
@Transactional
public void saveUser(User user) {
userMapper.insert(user);
throw new RuntimeException("模擬異常");
}
}
3.2 自調(diào)用(同類方法內(nèi)部調(diào)用)
類內(nèi)部的方法 A 調(diào)用另一個有 @Transactional 注解的方法 B,這屬于自調(diào)用。調(diào)用的是 this 對象本身的方法,而不是被 Spring 代理增強過的對象的方法,因此事務(wù)不會生效。
錯誤寫法:
@Service
public class UserService {
@Transactional
public void addUser(User user) {
userMapper.insert(user);
throw new RuntimeException("模擬異常");
}
public void process(User user) {
// 自調(diào)用,繞過代理
this.addUser(user);
}
}
正確寫法(通過代理調(diào)用):
@Service
public class UserService {
@Transactional
public void addUser(User user) {
userMapper.insert(user);
throw new RuntimeException("模擬異常");
}
}
@Service
public class OrderService {
@Autowired
private UserService userService;
public void process(User user) {
// 通過Spring代理調(diào)用,事務(wù)才生效
userService.addUser(user);
}
}
3.3 異常被捕獲“吃掉”
如果你在方法中捕獲了異常,并且沒有重新拋出,那么 Spring 的事務(wù)攔截器就感知不到異常,自然不會觸發(fā)回滾
錯誤寫法:
@Transactional
public void saveUser(User user) {
try {
userMapper.insert(user);
int i = 1 / 0; // 異常
} catch (Exception e) {
System.out.println("異常被吃掉");
}
}
正確寫法(繼續(xù)拋出):
@Transactional
public void saveUser(User user) {
try {
userMapper.insert(user);
int i = 1 / 0;
} catch (Exception e) {
throw new RuntimeException("拋出異常,保證回滾", e);
}
}
3.4 異常類型不匹配
默認情況下,只有 RuntimeException 和 Error 會觸發(fā)回滾。如果你拋出了 IOException, SQLException 等受檢異常,事務(wù)依然會提交
錯誤寫法:
@Transactional
public void saveUser(User user) throws IOException {
userMapper.insert(user);
throw new IOException("檢查型異常");
}
正確寫法:
@Transactional(rollbackFor = Exception.class)
public void saveUser(User user) throws IOException {
userMapper.insert(user);
throw new IOException("檢查型異常");
}
3.5 數(shù)據(jù)庫引擎不支持事務(wù)
以 MySQL 為例:InnoDB 支持事務(wù) 而 MyISAM 不支持事務(wù)
如果表使用了 MyISAM,即使 @Transactional 生效,也不會回滾
-- 檢查并修改表引擎 SHOW TABLE STATUS LIKE ‘your_table_name'; ALTER TABLE your_table_name ENGINE = InnoDB;
3.6 在異步方法中使用
在 @Async 標(biāo)記的異步方法中,操作是在一個新的線程中執(zhí)行的。此時的事務(wù)上下文和新線程的事務(wù)上下文是不同的,需要配置特殊的事務(wù)管理器來支持,否則容易失效
// 需要特殊配置的場景
@Service
public class AsyncService {
@Async
@Transactional // 普通配置下,此事務(wù)很可能失效
public void asyncTask() {
// 異步事務(wù)操作
}
}
3.7 未被Spring容器管理
這也是一些小伙伴粗心容易犯的錯,類沒有加上 @Service, @Component 等注解,它就不會被 Spring 掃描并創(chuàng)建為 Bean,那么它上面的 @Transactional 注解自然無效
// 錯誤示例:只是一個普通類,不是Spring Bean
// @Service 注釋掉了
public class MyService {
@Transactional // 這個注解完全沒用
public void doSomething() {
// ...
}
}
3.8 propagation 參數(shù)設(shè)置錯誤
錯誤地設(shè)置傳播行為可能導(dǎo)致意外情況。例如,在已經(jīng)存在事務(wù)的方法中,以 NOT_SUPPORTED 或 NEVER 模式調(diào)用新方法
// 可能非預(yù)期的行為
@Service
public class MainService {
@Transactional
public void mainMethod() {
// ... 主邏輯
subService.subMethod(); // 子方法掛起了當(dāng)前事務(wù)
}
}
@Service
public class SubService {
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void subMethod() {
// 此方法以非事務(wù)方式運行,即使操作失敗,也不影響mainMethod的事務(wù)
// 如果這里的操作需要原子性,那就出了問題
}
}
4. 結(jié)語
小伙伴們通過本文的講解,是否讓你對Spring事務(wù)@Transactional注解有了一個更深的認識?實際上我們大部分開發(fā)場景都使用默認配置即可,同時建議都按照 @Transactional(rollbackFor = Exception.class) 進行注解,業(yè)務(wù)處理過程避免拋出受檢異常IOException, SQLException 等,導(dǎo)致失效!
到此這篇關(guān)于Spring事務(wù)@Transactional失效的8大場景與解決方法的文章就介紹到這了,更多相關(guān)Spring事務(wù)@Transactional失效內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot中application.properties與application.yml區(qū)別小結(jié)
本文主要介紹了SpringBoot中application.properties與application.yml區(qū)別小結(jié),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-10-10
Java畢業(yè)設(shè)計實戰(zhàn)之線上水果超市商城的實現(xiàn)
這是一個使用了java+SSM+springboot+redis開發(fā)的網(wǎng)上水果超市商城,是一個畢業(yè)設(shè)計的實戰(zhàn)練習(xí),具有水果超市商城該有的所有功能,感興趣的朋友快來看看吧2022-01-01
spring boot教程之產(chǎn)生的背景及其優(yōu)勢
這篇文章主要介紹了spring boot教程之產(chǎn)生的背景及其優(yōu)勢的相關(guān)資料,需要的朋友可以參考下2022-08-08
SpringBoot請求轉(zhuǎn)發(fā)的方式小結(jié)
本文主要介紹了SpringBoot請求轉(zhuǎn)發(fā)的方式,一共有兩大類,一種是controller控制器轉(zhuǎn)發(fā)一種是使用HttpServletRequest進行轉(zhuǎn)發(fā),本文就詳細的介紹一下,感興趣的可以了解一下2023-09-09
IntelliJ?IDEA?2022.2.3最新激活圖文教程(親測有用永久激活)
今天給大家分享一個?IDEA?2022.2.3?的激活破解教程,全文通過文字+圖片的方式講解,手把手教你如何激活破解?IDEA,?只需要幾分鐘即可搞定,對idea2022.2.3激活碼感興趣的朋友跟隨小編一起看看吧2022-11-11
Spring Security組件一鍵接入驗證碼登錄和小程序登錄的詳細過程
這篇文章主要介紹了Spring Security 一鍵接入驗證碼登錄和小程序登錄,簡單介紹一下這個插件包的相關(guān)知識,本文結(jié)合示例代碼給大家介紹的非常詳細,需要的朋友參考下吧2022-04-04

