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

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

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

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

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

1. 事情起因

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

問題代碼如下:

參考代碼 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: 正常事務(wù) - 轉(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)賬后余額(應(yīng)該不變):");
        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)用 - 事務(wù)失效");
        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減少,事務(wù)未回滾):");
        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ù)未回滾,導(dǎo)致數(shù)據(jù)不一致");
        System.out.println();
        
        accountService.resetBalance("ACC001", new BigDecimal("10000"));
        accountService.resetBalance("ACC002", new BigDecimal("10000"));
        
        System.out.println("========================================");
        System.out.println("場景3: 私有方法 - 事務(wù)失效");
        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)賬后余額(事務(wù)未回滾):");
        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("  - 事務(wù)不會(huì)生效");
        System.out.println();
        
        accountService.resetBalance("ACC001", new BigDecimal("10000"));
        accountService.resetBalance("ACC002", new BigDecimal("10000"));
        
        System.out.println("========================================");
        System.out.println("場景4: try-catch吞掉異常 - 事務(wù)失效");
        System.out.println("========================================\n");
        
        accountService.transferWithException("ACC001", "ACC002", new BigDecimal("1000"));
        
        System.out.println("轉(zhuǎn)賬后余額(事務(wù)未回滾):");
        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("  - 事務(wù)管理器檢測不到異常");
        System.out.println("  - 事務(wù)不會(huì)回滾");
        System.out.println();
        
        accountService.resetBalance("ACC001", new BigDecimal("10000"));
        accountService.resetBalance("ACC002", new BigDecimal("10000"));
        
        System.out.println("========================================");
        System.out.println("場景5: 受檢異常 - 事務(wù)失效");
        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)賬后余額(事務(wù)未回滾):");
        System.out.println("  ACC001(張三): " + accountService.getBalance("ACC001"));
        System.out.println("  ACC002(李四): " + accountService.getBalance("ACC002"));
        System.out.println();
        System.out.println("問題分析:");
        System.out.println("  - 默認(rèn)只回滾RuntimeException和Error");
        System.out.println("  - 受檢異常(Exception)不會(huì)觸發(fā)回滾");
        System.out.println("  - 需要指定rollbackFor = Exception.class");
        System.out.println();
        
        System.exit(0);
    }
}

運(yùn)行結(jié)果:

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

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

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

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

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

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

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

========================================
場景3: 私有方法 - 事務(wù)失效
========================================

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

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

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

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

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

========================================
場景5: 受檢異常 - 事務(wù)失效
========================================

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

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

2. 原因分析

2.1 Spring AOP代理機(jī)制

Spring事務(wù)基于AOP代理實(shí)現(xiàn),理解代理機(jī)制是解決事務(wù)失效問題的關(guā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代理機(jī)制分析 ===\n");
        
        DemoService demoService = context.getBean(DemoService.class);
        
        System.out.println("========================================");
        System.out.println("1. 代理對象分析");
        System.out.println("========================================\n");
        
        System.out.println("Bean的實(shí)際類型: " + 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動(dòng)態(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("  - 目標(biāo)類: " + 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. 目標(biāo)對象 vs 代理對象");
        System.out.println("========================================\n");
        
        try {
            Class<?> targetClass = AopProxyUtils.ultimateTargetClass(demoService);
            System.out.println("最終目標(biāo)類: " + targetClass.getName());
            System.out.println();
            
            System.out.println("調(diào)用鏈分析:");
            System.out.println("  外部調(diào)用流程:");
            System.out.println("    調(diào)用方 -> 代理對象.transactionalMethod() -> 目標(biāo)對象.transactionalMethod()");
            System.out.println();
            System.out.println("  this調(diào)用流程:");
            System.out.println("    調(diào)用方 -> 代理對象.outerMethod() -> 目標(biāo)對象.outerMethod()");
            System.out.println("           -> this.internalMethod() [繞過代理,事務(wù)失效]");
            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. 事務(wù)失效原因總結(jié)");
        System.out.println("========================================\n");
        
        System.out.println("Spring事務(wù)基于AOP代理實(shí)現(xiàn):");
        System.out.println();
        System.out.println("代理模式工作原理:");
        System.out.println("  1. Spring容器啟動(dòng)時(shí),檢測到@Transactional注解");
        System.out.println("  2. 創(chuàng)建代理對象包裝目標(biāo)對象");
        System.out.println("  3. 代理對象在方法調(diào)用前后添加事務(wù)邏輯");
        System.out.println();
        System.out.println("this調(diào)用導(dǎo)致失效的原因:");
        System.out.println("  1. 外部調(diào)用通過代理對象進(jìn)入");
        System.out.println("  2. 代理對象開啟事務(wù)");
        System.out.println("  3. 代理對象調(diào)用目標(biāo)對象的方法");
        System.out.println("  4. 目標(biāo)對象內(nèi)部通過this調(diào)用其他方法");
        System.out.println("  5. this指向目標(biāo)對象本身,不是代理對象");
        System.out.println("  6. 繞過了代理,事務(wù)邏輯不執(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吞掉:事務(wù)管理器檢測不到異常");
        System.out.println("  4. 拋出受檢異常:默認(rèn)只回滾RuntimeException");
        System.out.println("  5. 類沒有被Spring管理:@Transactional無效");
        System.out.println("  6. 數(shù)據(jù)庫不支持事務(wù):如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() {
        }
    }
}

運(yùn)行結(jié)果:

=== Spring AOP代理機(jī)制分析 ===

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

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

是否是AOP代理對象: true

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

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

========================================
2. 目標(biāo)對象 vs 代理對象
========================================

最終目標(biāo)類: com.architect.pitfalls.transactional.analysis.AopProxyAnalysis$DemoService

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

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

========================================
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. 事務(wù)失效原因總結(jié)
========================================

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

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

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

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

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

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

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)點(diǎn):實(shí)現(xiàn)簡單,代碼改動(dòng)小
缺點(diǎ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)點(diǎn):職責(zé)單一,符合單一職責(zé)原則,易于測試和維護(hù)
缺點(diǎn):需要?jiǎng)?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)點(diǎn):不需要?jiǎng)?chuàng)建額外的類
缺點(diǎn):代碼侵入性強(qiáng),依賴AOP配置,不推薦在業(yè)務(wù)代碼中使用

3.4 方案對比

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

最佳實(shí)踐建議

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

4. 架構(gòu)思考

4.1 事務(wù)設(shè)計(jì)原則

單一職責(zé)原則

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

事務(wù)邊界設(shè)計(jì)

  • 事務(wù)應(yīng)該盡可能小
  • 避免長事務(wù)
  • 事務(wù)中避免遠(yuǎn)程調(diào)用

4.2 最佳實(shí)踐總結(jié)

代碼層面

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

團(tuán)隊(duì)規(guī)范

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

架構(gòu)設(shè)計(jì)

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

4.3 事務(wù)失效檢測方法

開發(fā)階段檢測

  1. 單元測試驗(yàn)證事務(wù)回滾
  2. 使用@Transactional的readOnly屬性測試
  3. 日志級(jí)別設(shè)置為DEBUG查看事務(wù)行為

運(yùn)行階段監(jiān)控

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

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

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

相關(guān)文章

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

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

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

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

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

    Spring Boot實(shí)戰(zhàn)之?dāng)?shù)據(jù)庫操作的示例代碼

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

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

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

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

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

    springboot實(shí)現(xiàn)配置多個(gè)yml文件

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

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

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

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

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

    使用maven打包/跳過某個(gè)modules

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

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

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

最新評論

沭阳县| 西林县| 龙山县| 阿坝| 凉城县| 灌阳县| 泸水县| 怀仁县| 丹巴县| 镇坪县| 乐亭县| 阿城市| 洪泽县| 铁力市| 溧水县| 建湖县| 突泉县| 潞城市| 正定县| 读书| 龙山县| 信宜市| 婺源县| 邵东县| 平邑县| 姜堰市| 乌鲁木齐县| 襄垣县| 东乡| 姚安县| 莱芜市| 河津市| 牟定县| 星子县| 五家渠市| 崇左市| 永安市| 长白| 沧州市| 资兴市| 阿克陶县|