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

Spring的@Transactional失效的幾種原因及解決方法

 更新時間:2026年07月22日 09:31:21   作者:linmoo2006  
本文主要介紹了Spring的@Transactional失效的幾種原因及解決方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

作為多年的Java開發(fā)經(jīng)驗,在開發(fā)過程中經(jīng)常會踩一些坑,本系列想通過一些案例分享,幫助其他開發(fā)者避免這些問題。

注意:由于框架不同版本改造會有些使用的不同,因此本次系列中使用JDK版本使用的是open-jdk21。

1. 事情起因

在一次金融系統(tǒng)升級中,發(fā)布新版本后,出現(xiàn)了嚴重的賬務數(shù)據(jù)不一致問題。經(jīng)過排查發(fā)現(xiàn),是因為轉(zhuǎn)賬服務中通過this調(diào)用了同類中的@Transactional方法,導致事務失效,資金被扣除但未正確入賬,造成了千萬級的資金差異。

問題代碼如下:

參考代碼 lesson11-transactional-failure 中的TransactionalFailureDemo.java

package com.architect.pitfalls.transactional.cause;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import java.math.BigDecimal;

@SpringBootApplication
public class TransactionalFailureDemo {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(TransactionalFailureDemo.class, args);
        AccountService accountService = context.getBean(AccountService.class);
        
        System.out.println("=== @Transactional失效場景演示 ===\n");
        
        System.out.println("初始賬戶余額:");
        System.out.println("  ACC001(張三): " + accountService.getBalance("ACC001"));
        System.out.println("  ACC002(李四): " + accountService.getBalance("ACC002"));
        System.out.println();
        
        System.out.println("========================================");
        System.out.println("場景1: 正常事務 - 轉(zhuǎn)賬失敗后回滾");
        System.out.println("========================================\n");
        
        try {
            accountService.transferWithTransaction("ACC001", "ACC002", new BigDecimal("1000"));
        } catch (Exception e) {
            System.out.println("轉(zhuǎn)賬異常: " + e.getMessage());
        }
        
        System.out.println("轉(zhuǎn)賬后余額(應該不變):");
        System.out.println("  ACC001(張三): " + accountService.getBalance("ACC001"));
        System.out.println("  ACC002(李四): " + accountService.getBalance("ACC002"));
        System.out.println();
        
        accountService.resetBalance("ACC001", new BigDecimal("10000"));
        accountService.resetBalance("ACC002", new BigDecimal("10000"));
        
        System.out.println("========================================");
        System.out.println("場景2: this調(diào)用 - 事務失效");
        System.out.println("========================================\n");
        
        try {
            accountService.transferWithThisCall("ACC001", "ACC002", new BigDecimal("1000"));
        } catch (Exception e) {
            System.out.println("轉(zhuǎn)賬異常: " + e.getMessage());
        }
        
        System.out.println("轉(zhuǎn)賬后余額(ACC001減少,事務未回滾):");
        System.out.println("  ACC001(張三): " + accountService.getBalance("ACC001"));
        System.out.println("  ACC002(李四): " + accountService.getBalance("ACC002"));
        System.out.println();
        System.out.println("問題分析:");
        System.out.println("  - transferWithThisCall()調(diào)用doTransferInternal()");
        System.out.println("  - 通過this調(diào)用繞過了代理對象");
        System.out.println("  - @Transactional注解失效");
        System.out.println("  - 異常后數(shù)據(jù)未回滾,導致數(shù)據(jù)不一致");
        System.out.println();
        
        accountService.resetBalance("ACC001", new BigDecimal("10000"));
        accountService.resetBalance("ACC002", new BigDecimal("10000"));
        
        System.out.println("========================================");
        System.out.println("場景3: 私有方法 - 事務失效");
        System.out.println("========================================\n");
        
        try {
            accountService.transferWithPrivateMethodCall("ACC001", "ACC002", new BigDecimal("1000"));
        } catch (Exception e) {
            System.out.println("轉(zhuǎn)賬異常: " + e.getMessage());
        }
        
        System.out.println("轉(zhuǎn)賬后余額(事務未回滾):");
        System.out.println("  ACC001(張三): " + accountService.getBalance("ACC001"));
        System.out.println("  ACC002(李四): " + accountService.getBalance("ACC002"));
        System.out.println();
        System.out.println("問題分析:");
        System.out.println("  - 私有方法上的@Transactional無效");
        System.out.println("  - Spring AOP無法代理私有方法");
        System.out.println("  - 事務不會生效");
        System.out.println();
        
        accountService.resetBalance("ACC001", new BigDecimal("10000"));
        accountService.resetBalance("ACC002", new BigDecimal("10000"));
        
        System.out.println("========================================");
        System.out.println("場景4: try-catch吞掉異常 - 事務失效");
        System.out.println("========================================\n");
        
        accountService.transferWithException("ACC001", "ACC002", new BigDecimal("1000"));
        
        System.out.println("轉(zhuǎn)賬后余額(事務未回滾):");
        System.out.println("  ACC001(張三): " + accountService.getBalance("ACC001"));
        System.out.println("  ACC002(李四): " + accountService.getBalance("ACC002"));
        System.out.println();
        System.out.println("問題分析:");
        System.out.println("  - 異常被try-catch捕獲");
        System.out.println("  - 事務管理器檢測不到異常");
        System.out.println("  - 事務不會回滾");
        System.out.println();
        
        accountService.resetBalance("ACC001", new BigDecimal("10000"));
        accountService.resetBalance("ACC002", new BigDecimal("10000"));
        
        System.out.println("========================================");
        System.out.println("場景5: 受檢異常 - 事務失效");
        System.out.println("========================================\n");
        
        try {
            accountService.transferWithCheckedException("ACC001", "ACC002", new BigDecimal("1000"));
        } catch (Exception e) {
            System.out.println("轉(zhuǎn)賬異常: " + e.getMessage());
        }
        
        System.out.println("轉(zhuǎn)賬后余額(事務未回滾):");
        System.out.println("  ACC001(張三): " + accountService.getBalance("ACC001"));
        System.out.println("  ACC002(李四): " + accountService.getBalance("ACC002"));
        System.out.println();
        System.out.println("問題分析:");
        System.out.println("  - 默認只回滾RuntimeException和Error");
        System.out.println("  - 受檢異常(Exception)不會觸發(fā)回滾");
        System.out.println("  - 需要指定rollbackFor = Exception.class");
        System.out.println();
        
        System.exit(0);
    }
}

運行結(jié)果:

=== @Transactional失效場景演示 ===

初始賬戶余額:
  ACC001(張三): 10000.00
  ACC002(李四): 10000.00

========================================
場景1: 正常事務 - 轉(zhuǎn)賬失敗后回滾
========================================

轉(zhuǎn)賬異常: 模擬轉(zhuǎn)賬異常
轉(zhuǎn)賬后余額(應該不變):
  ACC001(張三): 10000.00
  ACC002(李四): 10000.00

========================================
場景2: this調(diào)用 - 事務失效
========================================

轉(zhuǎn)賬異常: 模擬轉(zhuǎn)賬異常
轉(zhuǎn)賬后余額(ACC001減少,事務未回滾):
  ACC001(張三): 9000.00
  ACC002(李四): 11000.00

問題分析:
  - transferWithThisCall()調(diào)用doTransferInternal()
  - 通過this調(diào)用繞過了代理對象
  - @Transactional注解失效
  - 異常后數(shù)據(jù)未回滾,導致數(shù)據(jù)不一致

========================================
場景3: 私有方法 - 事務失效
========================================

轉(zhuǎn)賬異常: 模擬轉(zhuǎn)賬異常
轉(zhuǎn)賬后余額(事務未回滾):
  ACC001(張三): 9000.00
  ACC002(李四): 11000.00

問題分析:
  - 私有方法上的@Transactional無效
  - Spring AOP無法代理私有方法
  - 事務不會生效

========================================
場景4: try-catch吞掉異常 - 事務失效
========================================

轉(zhuǎn)賬后余額(事務未回滾):
  ACC001(張三): 9000.00
  ACC002(李四): 11000.00

問題分析:
  - 異常被try-catch捕獲
  - 事務管理器檢測不到異常
  - 事務不會回滾

========================================
場景5: 受檢異常 - 事務失效
========================================

轉(zhuǎn)賬異常: 模擬受檢異常
轉(zhuǎn)賬后余額(事務未回滾):
  ACC001(張三): 9000.00
  ACC002(李四): 11000.00

問題分析:
  - 默認只回滾RuntimeException和Error
  - 受檢異常(Exception)不會觸發(fā)回滾
  - 需要指定rollbackFor = Exception.class

2. 原因分析

2.1 Spring AOP代理機制

Spring事務基于AOP代理實現(xiàn),理解代理機制是解決事務失效問題的關鍵。

參考代碼 lesson11-transactional-failure 中的AopProxyAnalysis.java

package com.architect.pitfalls.transactional.analysis;

import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.Method;

@SpringBootApplication
public class AopProxyAnalysis {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(AopProxyAnalysis.class, args);
        
        System.out.println("=== Spring AOP代理機制分析 ===\n");
        
        DemoService demoService = context.getBean(DemoService.class);
        
        System.out.println("========================================");
        System.out.println("1. 代理對象分析");
        System.out.println("========================================\n");
        
        System.out.println("Bean的實際類型: " + demoService.getClass().getName());
        System.out.println();
        
        boolean isAopProxy = AopUtils.isAopProxy(demoService);
        System.out.println("是否是AOP代理對象: " + isAopProxy);
        System.out.println();
        
        if (isAopProxy) {
            System.out.println("代理類型分析:");
            System.out.println("  - 是否是JDK動態(tài)代理: " + AopUtils.isJdkDynamicProxy(demoService));
            System.out.println("  - 是否是CGLIB代理: " + AopUtils.isCglibProxy(demoService));
            System.out.println();
            
            try {
                Advised advised = (Advised) demoService;
                System.out.println("代理配置信息:");
                System.out.println("  - 目標類: " + advised.getTargetSource().getTargetClass().getName());
                System.out.println("  - 是否暴露代理: " + advised.isExposeProxy());
                System.out.println();
            } catch (Exception e) {
                System.out.println("獲取代理信息失敗: " + e.getMessage());
            }
        }
        
        System.out.println("========================================");
        System.out.println("2. 目標對象 vs 代理對象");
        System.out.println("========================================\n");
        
        try {
            Class<?> targetClass = AopProxyUtils.ultimateTargetClass(demoService);
            System.out.println("最終目標類: " + targetClass.getName());
            System.out.println();
            
            System.out.println("調(diào)用鏈分析:");
            System.out.println("  外部調(diào)用流程:");
            System.out.println("    調(diào)用方 -> 代理對象.transactionalMethod() -> 目標對象.transactionalMethod()");
            System.out.println();
            System.out.println("  this調(diào)用流程:");
            System.out.println("    調(diào)用方 -> 代理對象.outerMethod() -> 目標對象.outerMethod()");
            System.out.println("           -> this.internalMethod() [繞過代理,事務失效]");
            System.out.println();
        } catch (Exception e) {
            System.out.println("分析失敗: " + e.getMessage());
        }
        
        System.out.println("========================================");
        System.out.println("3. 方法注解分析");
        System.out.println("========================================\n");
        
        try {
            Class<?> targetClass = DemoService.class;
            
            System.out.println("分析DemoService的方法注解:\n");
            
            for (Method method : targetClass.getDeclaredMethods()) {
                Transactional tx = method.getAnnotation(Transactional.class);
                System.out.println("方法: " + method.getName());
                System.out.println("  修飾符: " + java.lang.reflect.Modifier.toString(method.getModifiers()));
                System.out.println("  @Transactional: " + (tx != null ? "存在" : "不存在"));
                if (tx != null) {
                    System.out.println("  rollbackFor: " + java.util.Arrays.toString(tx.rollbackFor()));
                }
                System.out.println();
            }
        } catch (Exception e) {
            System.out.println("方法分析失敗: " + e.getMessage());
        }
        
        System.out.println("========================================");
        System.out.println("4. 事務失效原因總結(jié)");
        System.out.println("========================================\n");
        
        System.out.println("Spring事務基于AOP代理實現(xiàn):");
        System.out.println();
        System.out.println("代理模式工作原理:");
        System.out.println("  1. Spring容器啟動時,檢測到@Transactional注解");
        System.out.println("  2. 創(chuàng)建代理對象包裝目標對象");
        System.out.println("  3. 代理對象在方法調(diào)用前后添加事務邏輯");
        System.out.println();
        System.out.println("this調(diào)用導致失效的原因:");
        System.out.println("  1. 外部調(diào)用通過代理對象進入");
        System.out.println("  2. 代理對象開啟事務");
        System.out.println("  3. 代理對象調(diào)用目標對象的方法");
        System.out.println("  4. 目標對象內(nèi)部通過this調(diào)用其他方法");
        System.out.println("  5. this指向目標對象本身,不是代理對象");
        System.out.println("  6. 繞過了代理,事務邏輯不執(zhí)行");
        System.out.println();
        System.out.println("其他失效場景:");
        System.out.println("  1. 方法是private:CGLIB無法代理私有方法");
        System.out.println("  2. 方法是final:CGLIB無法繼承final方法");
        System.out.println("  3. 異常被catch吞掉:事務管理器檢測不到異常");
        System.out.println("  4. 拋出受檢異常:默認只回滾RuntimeException");
        System.out.println("  5. 類沒有被Spring管理:@Transactional無效");
        System.out.println("  6. 數(shù)據(jù)庫不支持事務:如MySQL的MyISAM引擎");
        System.out.println();
        
        System.exit(0);
    }
    
    @Service
    public static class DemoService {
        
        @Transactional
        public void transactionalMethod() {
        }
        
        public void outerMethod() {
            internalMethod();
        }
        
        @Transactional
        public void internalMethod() {
        }
        
        @Transactional
        private void privateMethod() {
        }
        
        @Transactional(rollbackFor = Exception.class)
        public void methodWithRollbackFor() {
        }
    }
}

運行結(jié)果:

=== Spring AOP代理機制分析 ===

========================================
1. 代理對象分析
========================================

Bean的實際類型: com.architect.pitfalls.transactional.analysis.AopProxyAnalysis$DemoService$$SpringCGLIB$$0

是否是AOP代理對象: true

代理類型分析:
  - 是否是JDK動態(tài)代理: false
  - 是否是CGLIB代理: true

代理配置信息:
  - 目標類: com.architect.pitfalls.transactional.analysis.AopProxyAnalysis$DemoService
  - 是否暴露代理: false

========================================
2. 目標對象 vs 代理對象
========================================

最終目標類: com.architect.pitfalls.transactional.analysis.AopProxyAnalysis$DemoService

調(diào)用鏈分析:
  外部調(diào)用流程:
    調(diào)用方 -> 代理對象.transactionalMethod() -> 目標對象.transactionalMethod()

  this調(diào)用流程:
    調(diào)用方 -> 代理對象.outerMethod() -> 目標對象.outerMethod()
           -> this.internalMethod() [繞過代理,事務失效]

========================================
3. 方法注解分析
========================================

分析DemoService的方法注解:

方法: transactionalMethod
  修飾符: public
  @Transactional: 存在
  rollbackFor: []

方法: outerMethod
  修飾符: public
  @Transactional: 不存在

方法: internalMethod
  修飾符: public
  @Transactional: 存在
  rollbackFor: []

方法: privateMethod
  修飾符: private
  @Transactional: 存在
  rollbackFor: []

方法: methodWithRollbackFor
  修飾符: public
  @Transactional: 存在
  rollbackFor: [Ljava.lang.Exception;]

========================================
4. 事務失效原因總結(jié)
========================================

Spring事務基于AOP代理實現(xiàn):

代理模式工作原理:
  1. Spring容器啟動時,檢測到@Transactional注解
  2. 創(chuàng)建代理對象包裝目標對象
  3. 代理對象在方法調(diào)用前后添加事務邏輯

this調(diào)用導致失效的原因:
  1. 外部調(diào)用通過代理對象進入
  2. 代理對象開啟事務
  3. 代理對象調(diào)用目標對象的方法
  4. 目標對象內(nèi)部通過this調(diào)用其他方法
  5. this指向目標對象本身,不是代理對象
  6. 繞過了代理,事務邏輯不執(zhí)行

其他失效場景:
  1. 方法是private:CGLIB無法代理私有方法
  2. 方法是final:CGLIB無法繼承final方法
  3. 異常被catch吞掉:事務管理器檢測不到異常
  4. 拋出受檢異常:默認只回滾RuntimeException
  5. 類沒有被Spring管理:@Transactional無效
  6. 數(shù)據(jù)庫不支持事務:如MySQL的MyISAM引擎

2.2 事務失效場景總結(jié)

場景原因解決方案
this調(diào)用同類方法繞過代理對象自注入/拆分Service/AopContext
方法是privateCGLIB無法代理私有方法改為public
方法是finalCGLIB無法繼承final方法去掉final修飾符
異常被catch吞掉事務管理器檢測不到異常手動回滾或重新拋出
拋出受檢異常默認只回滾RuntimeException指定rollbackFor
類未被Spring管理@Transactional無效添加@Service等注解
數(shù)據(jù)庫不支持事務底層不支持使用支持事務的引擎

3. 解決方案

3.1 方案一:自注入(Self-Injection)

參考代碼 lesson11-transactional-failure 中的SelfInjectionSolution.java

package com.architect.pitfalls.transactional.solution;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;

@Service
public class SelfInjectionSolution {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private AccountServiceSelfInjection self;

    public BigDecimal getBalance(String accountNo) {
        return jdbcTemplate.queryForObject(
            "SELECT balance FROM account WHERE account_no = ?",
            BigDecimal.class, accountNo);
    }

    public void transferWithSelfInjection(String fromAccount, String toAccount, BigDecimal amount) {
        self.doTransferInternal(fromAccount, toAccount, amount);
    }

    @Transactional(rollbackFor = Exception.class)
    public void doTransferInternal(String fromAccount, String toAccount, BigDecimal amount) {
        BigDecimal fromBalance = getBalance(fromAccount);
        if (fromBalance.compareTo(amount) < 0) {
            throw new RuntimeException("余額不足");
        }
        jdbcTemplate.update("UPDATE account SET balance = balance - ? WHERE account_no = ?", amount, fromAccount);
        jdbcTemplate.update("UPDATE account SET balance = balance + ? WHERE account_no = ?", amount, toAccount);
        jdbcTemplate.update(
            "INSERT INTO transfer_log (from_account, to_account, amount, status) VALUES (?, ?, ?, ?)",
            fromAccount, toAccount, amount, "SUCCESS");
        throw new RuntimeException("模擬轉(zhuǎn)賬異常");
    }

    public void resetBalance(String accountNo, BigDecimal balance) {
        jdbcTemplate.update("UPDATE account SET balance = ? WHERE account_no = ?", balance, accountNo);
        jdbcTemplate.update("DELETE FROM transfer_log WHERE from_account = ? OR to_account = ?", accountNo, accountNo);
    }
}

@Service
class AccountServiceSelfInjection {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private AccountServiceSelfInjection self;

    public BigDecimal getBalance(String accountNo) {
        return jdbcTemplate.queryForObject(
            "SELECT balance FROM account WHERE account_no = ?",
            BigDecimal.class, accountNo);
    }

    public void transferWithSelfInjection(String fromAccount, String toAccount, BigDecimal amount) {
        self.doTransferInternal(fromAccount, toAccount, amount);
    }

    @Transactional(rollbackFor = Exception.class)
    public void doTransferInternal(String fromAccount, String toAccount, BigDecimal amount) {
        BigDecimal fromBalance = getBalance(fromAccount);
        if (fromBalance.compareTo(amount) < 0) {
            throw new RuntimeException("余額不足");
        }
        jdbcTemplate.update("UPDATE account SET balance = balance - ? WHERE account_no = ?", amount, fromAccount);
        jdbcTemplate.update("UPDATE account SET balance = balance + ? WHERE account_no = ?", amount, toAccount);
        jdbcTemplate.update(
            "INSERT INTO transfer_log (from_account, to_account, amount, status) VALUES (?, ?, ?, ?)",
            fromAccount, toAccount, amount, "SUCCESS");
        throw new RuntimeException("模擬轉(zhuǎn)賬異常");
    }

    public void resetBalance(String accountNo, BigDecimal balance) {
        jdbcTemplate.update("UPDATE account SET balance = ? WHERE account_no = ?", balance, accountNo);
        jdbcTemplate.update("DELETE FROM transfer_log WHERE from_account = ? OR to_account = ?", accountNo, accountNo);
    }
}

優(yōu)點:實現(xiàn)簡單,代碼改動小
缺點:自注入看起來有些反直覺,可能引起循環(huán)依賴警告

3.2 方案二:拆分Service(推薦)

參考代碼 lesson11-transactional-failure 中的SeparateServiceSolution.java

package com.architect.pitfalls.transactional.solution;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;

@Service
public class SeparateServiceSolution {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private TransferService transferService;

    public BigDecimal getBalance(String accountNo) {
        return jdbcTemplate.queryForObject(
            "SELECT balance FROM account WHERE account_no = ?",
            BigDecimal.class, accountNo);
    }

    public void transfer(String fromAccount, String toAccount, BigDecimal amount) {
        transferService.doTransfer(fromAccount, toAccount, amount);
    }

    public void resetBalance(String accountNo, BigDecimal balance) {
        jdbcTemplate.update("UPDATE account SET balance = ? WHERE account_no = ?", balance, accountNo);
        jdbcTemplate.update("DELETE FROM transfer_log WHERE from_account = ? OR to_account = ?", accountNo, accountNo);
    }
}

@Service
class TransferService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional(rollbackFor = Exception.class)
    public void doTransfer(String fromAccount, String toAccount, BigDecimal amount) {
        BigDecimal fromBalance = getBalance(fromAccount);
        if (fromBalance.compareTo(amount) < 0) {
            throw new RuntimeException("余額不足");
        }
        jdbcTemplate.update("UPDATE account SET balance = balance - ? WHERE account_no = ?", amount, fromAccount);
        jdbcTemplate.update("UPDATE account SET balance = balance + ? WHERE account_no = ?", amount, toAccount);
        jdbcTemplate.update(
            "INSERT INTO transfer_log (from_account, to_account, amount, status) VALUES (?, ?, ?, ?)",
            fromAccount, toAccount, amount, "SUCCESS");
        throw new RuntimeException("模擬轉(zhuǎn)賬異常");
    }

    private BigDecimal getBalance(String accountNo) {
        return jdbcTemplate.queryForObject(
            "SELECT balance FROM account WHERE account_no = ?",
            BigDecimal.class, accountNo);
    }
}

優(yōu)點:職責單一,符合單一職責原則,易于測試和維護
缺點:需要創(chuàng)建額外的Service類

3.3 方案三:AopContext.currentProxy()

參考代碼 lesson11-transactional-failure 中的AopContextSolution.java

package com.architect.pitfalls.transactional.solution;

import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;

@Service
public class AopContextSolution {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public BigDecimal getBalance(String accountNo) {
        return jdbcTemplate.queryForObject(
            "SELECT balance FROM account WHERE account_no = ?",
            BigDecimal.class, accountNo);
    }

    public void transferWithAopContext(String fromAccount, String toAccount, BigDecimal amount) {
        AopContextSolution proxy = (AopContextSolution) AopContext.currentProxy();
        proxy.doTransferInternal(fromAccount, toAccount, amount);
    }

    @Transactional(rollbackFor = Exception.class)
    public void doTransferInternal(String fromAccount, String toAccount, BigDecimal amount) {
        BigDecimal fromBalance = getBalance(fromAccount);
        if (fromBalance.compareTo(amount) < 0) {
            throw new RuntimeException("余額不足");
        }
        jdbcTemplate.update("UPDATE account SET balance = balance - ? WHERE account_no = ?", amount, fromAccount);
        jdbcTemplate.update("UPDATE account SET balance = balance + ? WHERE account_no = ?", amount, toAccount);
        jdbcTemplate.update(
            "INSERT INTO transfer_log (from_account, to_account, amount, status) VALUES (?, ?, ?, ?)",
            fromAccount, toAccount, amount, "SUCCESS");
        throw new RuntimeException("模擬轉(zhuǎn)賬異常");
    }

    public void resetBalance(String accountNo, BigDecimal balance) {
        jdbcTemplate.update("UPDATE account SET balance = ? WHERE account_no = ?", balance, accountNo);
        jdbcTemplate.update("DELETE FROM transfer_log WHERE from_account = ? OR to_account = ?", accountNo, accountNo);
    }
}

注意:需要在配置類上添加 @EnableAspectJAutoProxy(exposeProxy = true)

優(yōu)點:不需要創(chuàng)建額外的類
缺點:代碼侵入性強,依賴AOP配置,不推薦在業(yè)務代碼中使用

3.4 方案對比

方案復雜度推薦度適用場景
自注入★★★★簡單場景
拆分Service★★★★★推薦,職責清晰
AopContext★★★框架代碼

最佳實踐建議

  1. 優(yōu)先使用拆分Service方案,職責單一,易于維護
  2. 簡單場景可使用自注入方案
  3. 避免在同一個類中通過this調(diào)用事務方法
  4. 始終指定 rollbackFor = Exception.class
  5. 事務方法使用public修飾

4. 架構(gòu)思考

4.1 事務設計原則

單一職責原則

  • 一個Service類應該只負責一類業(yè)務
  • 事務方法應該獨立、原子
  • 避免在同一個類中形成復雜的調(diào)用鏈

事務邊界設計

  • 事務應該盡可能小
  • 避免長事務
  • 事務中避免遠程調(diào)用

4.2 最佳實踐總結(jié)

代碼層面

  • ? 事務方法使用public修飾
  • ? 始終指定rollbackFor = Exception.class
  • ? 避免在事務方法中使用try-catch吞掉異常
  • ? 避免在同一個類中通過this調(diào)用事務方法
  • ? 不要在private/final方法上使用@Transactional
  • ? 不要在事務方法中捕獲異常后不處理

團隊規(guī)范

  • 代碼審查:重點檢查事務方法的調(diào)用方式
  • 單元測試:確保事務回滾邏輯正確
  • 日志監(jiān)控:記錄事務異常,便于排查問題

架構(gòu)設計

  • 服務拆分:按業(yè)務領域拆分Service
  • 事務傳播:合理使用事務傳播行為
  • 異常處理:統(tǒng)一異常處理策略

4.3 事務失效檢測方法

開發(fā)階段檢測

  1. 單元測試驗證事務回滾
  2. 使用@Transactional的readOnly屬性測試
  3. 日志級別設置為DEBUG查看事務行為

運行階段監(jiān)控

  1. 監(jiān)控數(shù)據(jù)不一致情況
  2. 設置事務異常告警
  3. 定期審計事務日志

通過深入理解Spring事務的AOP代理機制,不僅能避免生產(chǎn)環(huán)境的數(shù)據(jù)不一致問題,更能設計出更加健壯的事務處理方案。在實際項目中,正確使用@Transactional注解至關重要,唯有深入理解其原理和限制,才能構(gòu)建真正可靠的數(shù)據(jù)一致性保障。

到此這篇關于Spring的@Transactional失效的幾種原因及解決方法的文章就介紹到這了,更多相關Spring @Transactional失效內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • SpringBoot3集成SLF4J+logback進行日志記錄的實現(xiàn)

    SpringBoot3集成SLF4J+logback進行日志記錄的實現(xiàn)

    本文主要介紹了SpringBoot3集成SLF4J+logback進行日志記錄的實現(xiàn),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • Java Map 按key排序和按Value排序的實現(xiàn)方法

    Java Map 按key排序和按Value排序的實現(xiàn)方法

    下面小編就為大家?guī)硪黄狫ava Map 按key排序和按Value排序的實現(xiàn)方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-08-08
  • Spring Boot實戰(zhàn)之數(shù)據(jù)庫操作的示例代碼

    Spring Boot實戰(zhàn)之數(shù)據(jù)庫操作的示例代碼

    本篇文章主要介紹了Spring Boot實戰(zhàn)之數(shù)據(jù)庫操作的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-01-01
  • SpringBoot實現(xiàn)圖片防盜鏈技術的原理分析與解決

    SpringBoot實現(xiàn)圖片防盜鏈技術的原理分析與解決

    這篇文章主要為大家詳細介紹了SpringBoot中實現(xiàn)圖片防盜鏈技術的原理分析與完整解決方案,文中的示例代碼講解詳細,需要的可以了解一下
    2025-07-07
  • SpringBoot下無節(jié)制和數(shù)據(jù)庫建立連接的問題及解決方法

    SpringBoot下無節(jié)制和數(shù)據(jù)庫建立連接的問題及解決方法

    本文介紹了無節(jié)制建立MySQL連接的危害,包括數(shù)據(jù)庫服務端資源耗盡、應用端性能劣化和監(jiān)控與運維困境,提出了系統(tǒng)性解決方案,包括連接池標準化配置、代碼規(guī)范與防御式編程、全鏈路監(jiān)控體系和架構(gòu)級優(yōu)化,感興趣的朋友一起看看吧
    2025-03-03
  • springboot實現(xiàn)配置多個yml文件

    springboot實現(xiàn)配置多個yml文件

    文章主要介紹了在Spring Boot項目中實現(xiàn)多環(huán)境配置的三種方式:方式一、多YAML文件配置;方式二、單YAML文件配置;方式三、在pom.xml中指定環(huán)境配置,每種方式都有其特點和適用場景
    2025-11-11
  • Java通過What、Why、How了解弱引用

    Java通過What、Why、How了解弱引用

    這篇文章主要介紹了Java通過What、Why、How了解弱引用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-03-03
  • Java中BeanUtil.copyProperties()處理大小寫字段轉(zhuǎn)換問題

    Java中BeanUtil.copyProperties()處理大小寫字段轉(zhuǎn)換問題

    本文主要介紹了Java中BeanUtil.copyProperties()處理大小寫字段轉(zhuǎn)換問題,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2026-02-02
  • 使用maven打包/跳過某個modules

    使用maven打包/跳過某個modules

    本文總結(jié)了在Maven項目中跳過或單獨構(gòu)建模塊的方法,包括使用`-pl`、`-am`和`-amd`參數(shù)來選擇性地執(zhí)行模塊構(gòu)建,以及通過`-Dmaven.test.skip`跳過測試,以提高構(gòu)建效率
    2024-12-12
  • Java創(chuàng)建內(nèi)部類對象實例詳解

    Java創(chuàng)建內(nèi)部類對象實例詳解

    這篇文章主要介紹了Java創(chuàng)建內(nèi)部類對象實例詳解的相關資料,需要的朋友可以參考下
    2017-05-05

最新評論

宁德市| 昌江| 龙岩市| 固安县| 呼和浩特市| 托克逊县| 雅江县| 凤山市| 台南市| 南昌市| 合肥市| 达州市| 彰化县| 开封市| 顺昌县| 原平市| 临猗县| 格尔木市| 清涧县| 息烽县| 永济市| 潜江市| 仪陇县| 宿松县| 金坛市| 清徐县| 吴堡县| 红桥区| 九台市| 剑川县| 遵义市| 墨玉县| 特克斯县| 阳信县| 泾源县| 凤城市| 本溪| 吉首市| 汽车| 安阳市| 宜兰市|