最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot事務注解@Transactional失效場景與解決方案

 更新時間:2025年08月04日 09:16:43   作者:青燈文案  
開發(fā)中我們經常會用到 Spring Boot 的事務注解,但往往會出現使用了 @Transactional 注解但是沒有生效的情況,下面就把這幾種不能生效的情況整理一下吧

開發(fā)中我們經常會用到 Spring Boot 的事務注解,為含有多種操作的方法添加事務,做到如果某一個環(huán)節(jié)出錯,全部回滾的效果。但是在開發(fā)中可能會因為不了解事務機制,而導致我們的方法使用了 @Transactional 注解但是沒有生效的情況,下面就把這幾種不能生效的情況整理一下。

一、非public方法(動態(tài)代理限制)

Spring 的事務管理本質上是通過 AOP 動態(tài)代理 實現的(JDK 動態(tài)代理或 CGLIB 代理)。

代理對象在調用目標方法時,會添加事務管理的邏輯(開啟事務、提交/回滾事務)。

然而,動態(tài)代理只能代理 public 方法。

如果你將 @Transactional 注解放在 protected、private 或默認(包級私有)方法上,Spring 在創(chuàng)建代理時無法為這些方法添加事務增強邏輯。

當你通過代理對象調用這些非 public 方法時,事務相關的代碼(如 beginTransaction(), commit(), rollback())不會被織入,因此事務管理完全失效。

所以,要確保所有需要事務管理的方法都是 public 的。這是 Spring AOP 代理機制的一個硬性限制。

二、自調用問題(類內部方法調用,不走代理)

這是 AOP 代理機制帶來的另一個典型問題。假設一個 Service 類中有兩個方法:

  • methodA():沒有 @Transactional 注解。
  • methodB():有 @Transactional 注解。

如果你在 methodA() 內部直接調用 this.methodB(),那么你調用的是 Service 類本身的 methodA()this 指向目標對象本身)。methodA() 內部調用 this.methodB(),是目標對象內部的方法調用。

這個調用完全不經過為該 Service 類生成的代理對象。

因為調用 methodB() 沒有經過代理對象,所以代理對象上附加的事務攔截邏輯根本不會被執(zhí)行。methodB() 雖然標注了 @Transactional,但在此次調用中完全失效。

解決方案有以下幾種:推薦重構代碼。

方案一:注入自身代理對象

開啟 exposeProxy:在配置類(如 @SpringBootApplication 主類)上添加 @EnableAspectJAutoProxy(exposeProxy = true)

在需要自調用事務方法的地方獲取代理對象:

((YourServiceClass) AopContext.currentProxy()).methodB();

AopContext.currentProxy() 獲取到當前方法執(zhí)行上下文中的代理對象(即被 Spring AOP 增強過的對象),通過這個代理對象調用 methodB(),就會走代理邏輯,事務攔截器生效。

這種方式不常用,會有缺點,引入了 Spring AOP 特定 API (AopContext),增加了代碼耦合度。

方案二:重構代碼(推薦)

將需要事務管理的業(yè)務邏輯 methodB() 抽取到另一個獨立的 Bean(如另一個 Service)中。然后在原來的 methodA() 中注入并使用這個新的 Bean 來調用 methodB()。這樣調用自然通過代理對象進行。

這是更符合設計原則(單一職責、依賴注入)的做法,避免了自調用問題,也降低了耦合。

方案三:使用 ApplicationContext 獲取 Bean

在類中注入 ApplicationContext,然后通過 ctx.getBean(YourServiceClass.class).methodB() 來調用。這樣獲取到的是代理 Bean,調用會走代理。

代碼略顯繁瑣,并且也需要依賴 Spring 容器。

三、異常類型不匹配(默認只回滾RuntimeException)

@Transactional 注解的 rollbackFor 屬性默認值是 RuntimeExceptionError。

  • 當方法拋出 RuntimeException 或其子類(如 NullPointerException, IllegalArgumentException)時,Spring 會回滾事務。
  • 當方法拋出檢查型異常(如 IOException, SQLException)時,Spring 默認會提交事務!

如果你在一個事務方法中拋出了自定義的業(yè)務異常(繼承自 Exception 而非 RuntimeException),或者拋出了其他檢查型異常,并且沒有顯式配置 rollbackFor,那么即使業(yè)務邏輯出錯拋出了異常,Spring 也會正常提交事務,導致數據不一致。

這時,我們要顯式指定 rollbackFor:在 @Transactional 注解中明確聲明哪些異常需要觸發(fā)回滾。

// 回滾所有 Exception 和自定義異常
@Transactional(rollbackFor = {Exception.class, YourCustomBusinessException.class}) 
public void transactionalMethod() throws Exception { ... }

或者修改默認行為(謹慎):雖然不推薦,但可以通過修改 Spring 的全局事務管理器配置來改變默認的回滾異常類型(例如改為回滾所有 Throwable)。

但這樣做風險較大,可能回滾不應該回滾的異常(如 OutOfMemoryError)。

最佳實踐還是根據具體業(yè)務在注解上顯式配置 rollbackFornoRollbackFor。

四、多線程切換(事務連接綁定ThreadLocal)

Spring 的事務管理核心是將數據庫連接(Connection)綁定到當前執(zhí)行線程(Thread)的 ThreadLocal 變量上。

一個事務從開始(beginTransaction)到提交/回滾(commit/rollback)期間,所有數據庫操作都使用這個綁定在當前線程 ThreadLocal 上的同一個 Connection,以此保證 ACID 特性。

如果你在一個事務方法內部啟動了一個新線程(new Thread()) 或者使用線程池(如 @Async)執(zhí)行數據庫操作,會出現以下情況:

  • 新線程擁有自己獨立的 ThreadLocal 存儲。
  • 新線程無法訪問到原始事務線程綁定的 Connection 對象。
  • 新線程中的數據庫操作會從連接池獲取一個新的、獨立的 Connection。
  • 這個新 Connection 不參與原始事務,其操作會在自身 autoCommit 模式下立即執(zhí)行(通常是自動提交),與原始事務完全隔離。

新線程中的數據庫操作成功與否不影響原始事務的提交或回滾,反之亦然。破壞了事務的原子性(Atomicity)。原始事務回滾不會回滾新線程中的操作;新線程操作失敗也不會導致原始事務回滾。

解決方案:處理多線程下的數據一致性非常復雜,沒有銀彈:

  • 避免在事務方法內開啟異步線程執(zhí)行 DB 操作:**這是最根本的預防措施。將需要在同一事務中完成的操作放在同一個線程內執(zhí)行。
  • 編程式事務管理: 在新線程內部,使用 TransactionTemplate 手動管理事務邊界。但這只是讓新線程內部操作具有事務性,無法與原始線程的事務合并成一個原子事務。
  • 分布式事務:如果業(yè)務強要求跨線程的 ACID,可能需要引入分布式事務管理器(如 Seata, Atomikos)來處理這種跨 資源(不同線程可視為不同資源管理者)的場景,但代價高昂且復雜。
  • 設計補償機制: 在業(yè)務層設計最終一致性方案(如 Saga 模式),通過記錄操作日志、發(fā)送消息、定時任務補償等方式,在異步操作失敗后嘗試回滾或修正原始事務已提交的操作。這是更常見的處理異步事務一致性的實踐。

五、錯誤傳播行為(如:PROPAGATION_NOT_SUPPORTED掛起事務)

@Transactionalpropagation 屬性定義了當前方法的事務如何與已存在的事務進行交互。使用不當會導致事務行為不符合預期。

PROPAGATION_NOT_SUPPORTED : 不支持事務。如果當前存在事務,則掛起(Suspend) 這個事務;然后以非事務方式執(zhí)行當前方法。方法執(zhí)行完畢后,之前掛起的事務恢復(Resume)。

假設方法 outer() 開啟了一個事務(Propagation.REQUIRED),在其內部調用 inner() 方法,而 inner() 被標注為 @Transactional(propagation = Propagation.NOT_SUPPORTED),當執(zhí)行到 inner() 時:

  • 系統(tǒng)檢測到當前存在 outer() 開啟的事務。
  • 根據 NOT_SUPPORTED 語義,掛起 outer() 的事務。
  • inner() 方法在無事務狀態(tài)下執(zhí)行(相當于 autoCommit=true)。
  • inner() 方法執(zhí)行完畢(無論成功失敗,其操作已立即提交)。
  • 恢復 outer() 的事務,繼續(xù)執(zhí)行 outer() 剩余代碼。

結果是 inner() 方法中的數據庫操作不受 outer() 事務控制。即使 outer() 最終因異常回滾,inner() 中已提交的操作不會被回滾!這通常不是開發(fā)者想要的效果,極易造成數據不一致。

其他易錯傳播行為:

  • PROPAGATION_NEVER: 要求不能存在事務。如果調用者在一個事務中調用了標記為 NEVER 的方法,會直接拋出 IllegalTransactionStateException 異常。
  • PROPAGATION_SUPPORTS: 如果當前存在事務,就加入該事務;如果沒有,就以非事務方式執(zhí)行。關鍵點在于非事務方式。如果方法中有多個操作且需要原子性,而外部又恰好沒有事務,這些操作就會各自獨立提交。
  • PROPAGATION_REQUIRES_NEW: 總是開啟一個全新的、獨立的事務。會掛起外部事務(如果存在)。新事務的提交/回滾與外部事務互不影響。注意: 這雖然創(chuàng)建了新事務,但不同于自調用失效,它是有效的(通過代理調用)。它的陷阱在于開發(fā)者可能誤以為新事務是外部事務的一部分,其實它們是獨立的。

解決方案:

  • 深入理解傳播行為: 務必清楚每種傳播行為(REQUIRED, REQUIRES_NEW, SUPPORTS, MANDATORY, NOT_SUPPORTED, NEVER, NESTED)的精確語義。
  • 謹慎選擇傳播行為: 默認使用 Propagation.REQUIRED 通常能滿足大多數場景(加入現有事務,沒有則新建)。只有在有明確且充分理由時才使用其他傳播行為。
  • 代碼審查與測試: 對使用了非默認傳播行為的代碼進行重點審查,并通過單元測試、集成測試模擬各種調用鏈路,驗證事務邊界和回滾行為是否符合預期。特別注意跨方法、跨服務調用時的事務傳播。

六、總結

Spring Boot 事務失效的核心原因通常圍繞:

  • AOP 代理機制的限制(非 public、自調用)
  • 異常處理機制(默認回滾異常類型)
  • 資源綁定機制(ThreadLocal 導致多線程失效)
  • 配置錯誤(傳播行為誤用)

解決這些問題需要深入理解 Spring 事務管理的底層原理(代理、ThreadLocal、異常回滾規(guī)則、傳播語義),并在編碼和配置時保持謹慎,遵循最佳實踐(如方法 public、避免自調用、顯式指定 rollbackFor、理解傳播行為、避免事務內跨線程操作 DB)。

到此這篇關于SpringBoot事務注解@Transactional失效場景與解決方案的文章就介紹到這了,更多相關SpringBoot @Transactional失效解決內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

最新評論

新绛县| 泾阳县| 曲松县| 南和县| 万盛区| 清涧县| 育儿| 井研县| 泉州市| 阿城市| 黑龙江省| 云霄县| 长岛县| 北川| 五常市| 密云县| 新竹县| 万安县| 嘉定区| 陈巴尔虎旗| 孝义市| 四会市| 炎陵县| 左云县| 定日县| 台山市| 宁武县| 濉溪县| 雷州市| 专栏| 五河县| 洛川县| 全南县| 景洪市| 永嘉县| 武威市| 河池市| 黄平县| 靖宇县| 辉县市| 吴旗县|