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

Spring事務(wù)失效的6種常見典型場景分析與解決方案

 更新時間:2026年02月08日 11:30:38   作者:GRF久睡成癮  
Spring事務(wù)失效的原因多種多樣,常見的問題包括方法可見性、異常處理、傳播行為配置、代理機制等,這篇文章主要介紹了Spring事務(wù)失效的6種常見典型場景分析與解決方案的相關(guān)資料,需要的朋友可以參考下

前言

在 Spring 應(yīng)用程序開發(fā)中,聲明式事務(wù)(@Transactional)是保證數(shù)據(jù)一致性的核心機制。然而,在實際應(yīng)用中,開發(fā)者常遇到注解已添加但事務(wù)未回滾的情況。本文基于最近在領(lǐng)券業(yè)務(wù)中遇到的并發(fā)與事務(wù)沖突問題,深入分析 Spring 事務(wù)失效的六種典型場景,并提供相應(yīng)的解決方案。

場景一:事務(wù)方法訪問權(quán)限非 public

問題描述

@Transactional 注解應(yīng)用于非 public 修飾的方法(如 protectedprivate 或包級私有方法)時,事務(wù)將失效。

示例代碼

@Service
public class UserService {
    
    @Transactional
    protected void updateUser(User user) {
        // 業(yè)務(wù)邏輯
    }
}

原因分析

Spring 的聲明式事務(wù)依賴于 AOP。Spring AOP 的默認(rèn)實現(xiàn)(無論是 JDK 動態(tài)代理還是 CGLIB)通常要求目標(biāo)方法必須是public,以便代理對象能夠正確攔截并增強該方法。

此外,Spring 源碼中的 AbstractFallbackTransactionAttributeSource.computeTransactionAttribute 方法顯式規(guī)定了僅處理 public 方法:

if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
    return null; // 非 public 方法返回 null,不應(yīng)用事務(wù)配置
}

解決方案

確保被 @Transactional 注解修飾的方法訪問權(quán)限為 public。

場景二:同一類內(nèi)部方法自調(diào)用

這是開發(fā)中最易被忽視的失效原因。

問題描述

在一個沒有事務(wù)注解的普通方法內(nèi)部,直接調(diào)用同一類中被 @Transactional 注解修飾的方法,事務(wù)將失效。

示例代碼

@Service
public class UserCouponServiceImpl implements IUserCouponService {
    
    // 入口方法,無事務(wù)注解,但在內(nèi)部調(diào)用了事務(wù)方法
    @Override
    public void receiveCoupon(Long couponId) {
        // ... 前置校驗
        
        // 【關(guān)鍵點】內(nèi)部直接調(diào)用事務(wù)方法,事務(wù)失效
        this.saveUserCouponAndUpdateCoupon(coupon, userId);
    }
    
    @Transactional
    public void saveUserCouponAndUpdateCoupon(Coupon coupon, Long userId){
        // 數(shù)據(jù)庫操作:扣減庫存、保存記錄
        couponMapper.plusIssueNum(couponId);
        save(userCoupon);
    }
}

原因分析:為什么this調(diào)用會導(dǎo)致事務(wù)失效?

Spring 的聲明式事務(wù)是基于 AOP動態(tài)代理 實現(xiàn)的。

  1. 代理對象的攔截機制:當(dāng) Spring 容器啟動時,會為使用了 @Transactional 的 Bean 創(chuàng)建一個代理對象。這個代理對象持有一個指向原始目標(biāo)對象(Target,即 UserCouponServiceImpl 實例)的引用。
  2. 外部調(diào)用的流程:當(dāng) Controller 調(diào)用 userCouponService.receiveCoupon(...) 時,實際上是在調(diào)用代理對象的方法。代理對象會檢查該方法是否有 @Transactional 注解。
    • 如果有,代理對象會在調(diào)用目標(biāo)方法前開啟事務(wù),調(diào)用后提交事務(wù)。
    • 如果沒有(如 receiveCoupon),代理對象會直接將請求轉(zhuǎn)發(fā)給原始目標(biāo)對象。
  3. 內(nèi)部調(diào)用的陷阱:一旦進(jìn)入原始目標(biāo)對象的方法內(nèi)部(如 receiveCoupon 執(zhí)行中),代碼執(zhí)行流就已經(jīng)脫離了代理對象的控制。此時,代碼中直接調(diào)用的 saveUserCouponAndUpdateCoupon(...) 等同于 this.saveUserCouponAndUpdateCoupon(...)。這里的 this 指向的是原始目標(biāo)對象本身,而不是代理對象。
  4. 結(jié)論:由于繞過了代理對象直接使用service對象,Spring 的事務(wù)攔截器無法介入,最終導(dǎo)致AOP實現(xiàn)的事務(wù)邏輯根本沒有執(zhí)行。

解決方案

方案 A:直接給入口方法添加事務(wù)注解(常規(guī)解法)

最簡單的解決方法是給入口方法 receiveCoupon 也添加 @Transactional 注解。這樣,事務(wù)在進(jìn)入 receiveCoupon 時就已經(jīng)開啟,后續(xù)的調(diào)用都在同一個事務(wù)上下文中運行。

@Transactional // 簡單粗暴,直接加事務(wù)
public void receiveCoupon(Long couponId) {
    saveUserCouponAndUpdateCoupon(coupon, userId);
}

但是,在并發(fā)場景下,這種方案往往不可行。

方案 B:強制使用代理對象調(diào)用(高并發(fā)場景推薦)

在我的領(lǐng)券業(yè)務(wù)中,為了防止超賣,我們需要使用 synchronized 鎖來控制并發(fā)。

  • 如果使用方案 A:事務(wù)包裹了鎖(Transaction 包含 synchronized)。
    • 執(zhí)行順序:開啟事務(wù) -> 加鎖 -> 執(zhí)行業(yè)務(wù) -> 解鎖 -> 提交事務(wù)
    • 風(fēng)險:線程 A 解鎖后,事務(wù)尚未提交。此時線程 B 獲取鎖并讀取數(shù)據(jù),讀到的仍然是舊數(shù)據(jù)(因為線程 A 的事務(wù)還沒提交),導(dǎo)致鎖失效,發(fā)生超賣。
  • 為了解決鎖失效:我們必須保證鎖的范圍大于事務(wù)(synchronized 包含 Transaction)。
    • 執(zhí)行順序:加鎖 -> 開啟事務(wù) -> 執(zhí)行業(yè)務(wù) -> 提交事務(wù) -> 解鎖。
    • 這就要求 receiveCoupon 方法不能加事務(wù)注解(它是加鎖的地方),只有內(nèi)部調(diào)用的 saveUserCouponAndUpdateCoupon 方法需要加事務(wù)。

這就回到了最初的問題:內(nèi)部調(diào)用會導(dǎo)致事務(wù)失效。

為了同時滿足“鎖包事務(wù)”和“事務(wù)生效”兩個條件,我們必須在 receiveCoupon 內(nèi)部,手動獲取當(dāng)前的代理對象來調(diào)用事務(wù)方法,強行讓調(diào)用邏輯重新經(jīng)過 Spring AOP 的攔截器鏈。

實現(xiàn)步驟:

  1. 引入 AspectJ 依賴

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
    </dependency>
    
  2. 開啟代理暴露(在啟動類或配置類):

    @EnableAspectJAutoProxy(exposeProxy = true)
    
  3. 使用 AopContext 獲取代理對象并調(diào)用
    參考 UserCouponServiceImpl.java 中的實現(xiàn):

    public void receiveCoupon(Long couponId) {
        // ... 省略校驗邏輯
        
        synchronized (userId.toString().intern()) {
            // 【核心代碼】從 ThreadLocal 中獲取當(dāng)前 AOP 代理對象
            IUserCouponService proxy = (IUserCouponService) AopContext.currentProxy();
            
            // 通過代理對象調(diào)用,觸發(fā)事務(wù)切面邏輯
            proxy.saveUserCouponAndUpdateCoupon(coupon, userId);
        }
    }
    

通過這種在service調(diào)用的方法使用代理對象調(diào)用的方式,我們既控制了事務(wù)的粒度(只包裹核心數(shù)據(jù)庫操作),又避免了內(nèi)部調(diào)用繞過代理機制導(dǎo)致的事務(wù)失效,完美解決了高并發(fā)下的數(shù)據(jù)一致性問題。

場景三:事務(wù)方法內(nèi)部捕獲異常且未拋出

問題描述

在事務(wù)方法內(nèi)部使用 try-catch 塊捕獲了異常,且在 catch 塊中未再次拋出異常,導(dǎo)致事務(wù)提交而非回滾。

示例代碼

@Service
public class OrderService {

    @Transactional
    public void createOrder() {
        try {
            insertOrder();
            reduceStock(); // 假設(shè)此處拋出異常
        } catch (Exception e) {
            e.printStackTrace();
            // 異常被吞噬,未向外拋出
        }
    }
}

原因分析

Spring AOP 代理對象在調(diào)用目標(biāo)方法后,會檢查方法執(zhí)行過程中是否拋出了異常。

  • 如果捕獲到異常,且異常類型符合回滾規(guī)則,則執(zhí)行回滾。
  • 如果目標(biāo)方法內(nèi)部自行處理了異常(即 catch 后未拋出),代理對象將認(rèn)為方法執(zhí)行成功,從而提交事務(wù)。

解決方案

  1. 避免吞噬異常:在 catch 塊處理完日志或其他邏輯后,務(wù)必將異常再次拋出。
  2. 手動標(biāo)記回滾:如果業(yè)務(wù)邏輯要求不能拋出異常,則必須在 catch 塊中手動標(biāo)記事務(wù)狀態(tài)為回滾:
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    

場景四:異常類型不匹配

問題描述

方法拋出了異常,但異常類型是檢查型異常(Checked Exception,如 IOExceptionSQLException),而 @Transactional 使用了默認(rèn)配置。

示例代碼

@Service
public class OrderService {

    @Transactional // 使用默認(rèn)配置
    public void createOrder() throws IOException {
        insertOrder();
        if (errorCondition) {
            throw new IOException("IO Error");
        }
    }
}

原因分析

Spring 的 @Transactional 注解默認(rèn)配置的 rollbackFor 屬性僅包含 RuntimeExceptionError。這意味著,對于所有繼承自 Exception 但非 RuntimeException 的檢查型異常,Spring 默認(rèn)不會觸發(fā)回滾。

解決方案

顯式配置 rollbackFor 屬性,建議指定為 Exception.class 以覆蓋所有異常類型:

@Transactional(rollbackFor = Exception.class)

場景五:事務(wù)傳播行為配置錯誤

問題描述

在嵌套事務(wù)場景中,內(nèi)部方法的傳播行為配置導(dǎo)致其事務(wù)獨立于外部事務(wù),從而破壞了整體原子性。

示例代碼

@Service
public class OrderService {
    @Transactional
    public void createOrder(){
        insertOrder();
        try {
            stockService.reduceStock(); // 即使外部回滾,此方法可能已提交
        } catch (Exception e) {
            // ...
        }
        throw new RuntimeException("Error");
    }
}

@Service
public class StockService {
    // REQUIRES_NEW 開啟獨立事務(wù)
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void reduceStock() {
        // ...
    }
}

原因分析

Propagation.REQUIRES_NEW 策略會掛起當(dāng)前事務(wù),并開啟一個新的物理事務(wù)。即使外部調(diào)用方(createOrder)后續(xù)發(fā)生異常并回滾,reduceStock 方法的獨立事務(wù)一旦提交,其數(shù)據(jù)變更將永久生效,導(dǎo)致數(shù)據(jù)不一致。

解決方案

根據(jù)業(yè)務(wù)一致性需求正確選擇傳播行為。對于大多數(shù)需要保證原子性的組合操作,應(yīng)使用默認(rèn)的 Propagation.REQUIRED,確保所有方法在同一個邏輯事務(wù)中運行。

場景六:類未被 Spring 容器管理

問題描述

調(diào)用 @Transactional 方法的對象實例并非由 Spring 容器創(chuàng)建和管理。

示例代碼

// 缺少 @Service 或 @Component 注解
public class OrderService {
    @Transactional
    public void createOrder() {
        // ...
    }
}

或者:

OrderService service = new OrderService(); // 手動 new 實例
service.createOrder();

原因分析

Spring 的聲明式事務(wù)完全依賴于 IoC 容器對 Bean 的生命周期管理和 AOP 代理生成。如果一個類沒有被注冊為 Spring Bean(缺少 @Service、@Component 等注解),或者對象是通過 new 關(guān)鍵字手動實例化的,Spring 容器無法感知該對象,也就無法為其創(chuàng)建代理并織入事務(wù)切面邏輯。

解決方案

  1. 確保業(yè)務(wù)類上添加了 @Service、@Component 等組件注解。
  2. 在其他組件中使用該類時,必須通過依賴注入(@Autowired 或構(gòu)造器注入)獲取實例,嚴(yán)禁手動實例化。

總結(jié)

Spring 事務(wù)失效問題通常源于對 Spring AOP 代理機制理解的偏差。在排查此類問題時,應(yīng)重點關(guān)注以下三個維度:

  1. 代理機制:是否存在對象自調(diào)用、類是否被容器管理、方法可見性是否合規(guī)。
  2. 異常處理:異常是否被捕獲吞噬、異常類型是否在回滾范圍內(nèi)。
  3. 事務(wù)配置:傳播行為是否符合業(yè)務(wù)預(yù)期。

在我的項目領(lǐng)券業(yè)務(wù)場景中,我們?yōu)榱思骖櫜l(fā)控制(synchronized)和事務(wù)原子性,采用了手動獲取代理對象(AopContext.currentProxy())的方案,有效解決了自調(diào)用導(dǎo)致的事務(wù)失效問題。

到此這篇關(guān)于Spring事務(wù)失效的6種常見典型場景分析與解決方案的文章就介紹到這了,更多相關(guān)Spring事務(wù)失效解決內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Spring Boot與React集成的示例代碼

    Spring Boot與React集成的示例代碼

    這篇文章主要介紹了Spring Boot與React集成的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-11-11
  • Java?Selenide?簡介與用法

    Java?Selenide?簡介與用法

    Selenium?是目前用的最廣泛的Web?UI?自動化測試框架,本文給大家介紹下Java?Selenide使用,感興趣的朋友一起看看吧
    2022-01-01
  • 詳解Java中Dijkstra(迪杰斯特拉)算法的圖解與實現(xiàn)

    詳解Java中Dijkstra(迪杰斯特拉)算法的圖解與實現(xiàn)

    Dijkstra(迪杰斯特拉)算法是典型的單源最短路徑算法,用于計算一個節(jié)點到其他所有節(jié)點的最短路徑。本文將詳解該算法的圖解與實現(xiàn),需要的可以參考一下
    2022-05-05
  • SpringBoot分布式WebSocket的實現(xiàn)指南

    SpringBoot分布式WebSocket的實現(xiàn)指南

    在現(xiàn)代Web應(yīng)用中,實時通信已成為基本需求,而WebSocket是實現(xiàn)這一功能的核心技術(shù),本文將詳細(xì)介紹如何在Spring Boot項目中實現(xiàn)分布式WebSocket,包括完整的技術(shù)方案、實現(xiàn)步驟和核心代碼,需要的朋友可以參考下
    2025-10-10
  • Spring實戰(zhàn)之設(shè)置普通屬性值的方法示例

    Spring實戰(zhàn)之設(shè)置普通屬性值的方法示例

    這篇文章主要介紹了Spring實戰(zhàn)之設(shè)置普通屬性值的方法,結(jié)合實例形式分析了Spring設(shè)置普通屬性值的方法及相關(guān)操作注意事項,需要的朋友可以參考下
    2019-11-11
  • SpringBoot結(jié)合Redis實現(xiàn)會話管理功能

    SpringBoot結(jié)合Redis實現(xiàn)會話管理功能

    在Web應(yīng)用程序中,會話管理是一項重要的任務(wù),它涉及跟蹤用戶的身份驗證狀態(tài)和其他相關(guān)信息,以確保用戶在與應(yīng)用程序交互時的連續(xù)性和安全性,在本文中,我們將探討如何使用Spring Boot和Redis實現(xiàn)高效的會話管理
    2023-06-06
  • 一文帶你了解Java創(chuàng)建型設(shè)計模式之原型模式

    一文帶你了解Java創(chuàng)建型設(shè)計模式之原型模式

    原型模式其實就是從一個對象在創(chuàng)建另外一個可定制的對象,不需要知道任何創(chuàng)建的細(xì)節(jié)。本文就來通過示例為大家詳細(xì)聊聊原型模式,需要的可以參考一下
    2022-09-09
  • 詳解Java的線程的優(yōu)先級以及死鎖

    詳解Java的線程的優(yōu)先級以及死鎖

    這篇文章主要介紹了詳解Java的線程的優(yōu)先級以及死鎖,線程是Java編程學(xué)習(xí)中的重要知識,需要的朋友可以參考下
    2015-09-09
  • Java實戰(zhàn)房屋租賃網(wǎng)的實現(xiàn)流程

    Java實戰(zhàn)房屋租賃網(wǎng)的實現(xiàn)流程

    讀萬卷書不如行萬里路,只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+SSM+jsp+mysql+maven實現(xiàn)一個房屋租賃網(wǎng)站,大家可以在過程中查缺補漏,提升水平
    2021-11-11
  • Java泛型枚舉Annotation接口詳細(xì)解讀與Eclipse發(fā)展

    Java泛型枚舉Annotation接口詳細(xì)解讀與Eclipse發(fā)展

    這篇文章主要給大家介紹了關(guān)于Java中方法使用的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-08-08

最新評論

拜城县| 阿拉善右旗| 黑水县| 长汀县| 绥中县| 吉木萨尔县| 萨迦县| 灌云县| 黔西县| 青阳县| 彩票| 石河子市| 永年县| 荔浦县| 盐津县| 曲沃县| 阿城市| 沂水县| 南丹县| 辽宁省| 栾川县| 濮阳市| 四会市| 宣汉县| 敦煌市| 本溪| 镇巴县| 威宁| 阳朔县| 建德市| 颍上县| 仁怀市| 青田县| 和静县| 灯塔市| 南华县| 林西县| 英山县| 通渭县| 天全县| 旌德县|