java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Spring @Transactional失效

Spring的@Transactional失效的几种原因及解决方法

作者:linmoo2006

本文主要介绍了Spring的@Transactional失效的几种原因及解决方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

作为多年的Java开发经验,在开发过程中经常会踩一些坑,本系列想通过一些案例分享,帮助其他开发者避免这些问题。

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

1. 事情起因

在一次金融系统升级中,发布新版本后,出现了严重的账务数据不一致问题。经过排查发现,是因为转账服务中通过this调用了同类中的@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: 正常事务 - 转账失败后回滚");
        System.out.println("========================================\n");
        
        try {
            accountService.transferWithTransaction("ACC001", "ACC002", new BigDecimal("1000"));
        } catch (Exception e) {
            System.out.println("转账异常: " + e.getMessage());
        }
        
        System.out.println("转账后余额(应该不变):");
        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调用 - 事务失效");
        System.out.println("========================================\n");
        
        try {
            accountService.transferWithThisCall("ACC001", "ACC002", new BigDecimal("1000"));
        } catch (Exception e) {
            System.out.println("转账异常: " + e.getMessage());
        }
        
        System.out.println("转账后余额(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()调用doTransferInternal()");
        System.out.println("  - 通过this调用绕过了代理对象");
        System.out.println("  - @Transactional注解失效");
        System.out.println("  - 异常后数据未回滚,导致数据不一致");
        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("转账异常: " + e.getMessage());
        }
        
        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("  - 私有方法上的@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("转账后余额(事务未回滚):");
        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("转账异常: " + e.getMessage());
        }
        
        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("  - 默认只回滚RuntimeException和Error");
        System.out.println("  - 受检异常(Exception)不会触发回滚");
        System.out.println("  - 需要指定rollbackFor = Exception.class");
        System.out.println();
        
        System.exit(0);
    }
}

运行结果:

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

初始账户余额:
  ACC001(张三): 10000.00
  ACC002(李四): 10000.00

========================================
场景1: 正常事务 - 转账失败后回滚
========================================

转账异常: 模拟转账异常
转账后余额(应该不变):
  ACC001(张三): 10000.00
  ACC002(李四): 10000.00

========================================
场景2: this调用 - 事务失效
========================================

转账异常: 模拟转账异常
转账后余额(ACC001减少,事务未回滚):
  ACC001(张三): 9000.00
  ACC002(李四): 11000.00

问题分析:
  - transferWithThisCall()调用doTransferInternal()
  - 通过this调用绕过了代理对象
  - @Transactional注解失效
  - 异常后数据未回滚,导致数据不一致

========================================
场景3: 私有方法 - 事务失效
========================================

转账异常: 模拟转账异常
转账后余额(事务未回滚):
  ACC001(张三): 9000.00
  ACC002(李四): 11000.00

问题分析:
  - 私有方法上的@Transactional无效
  - Spring AOP无法代理私有方法
  - 事务不会生效

========================================
场景4: try-catch吞掉异常 - 事务失效
========================================

转账后余额(事务未回滚):
  ACC001(张三): 9000.00
  ACC002(李四): 11000.00

问题分析:
  - 异常被try-catch捕获
  - 事务管理器检测不到异常
  - 事务不会回滚

========================================
场景5: 受检异常 - 事务失效
========================================

转账异常: 模拟受检异常
转账后余额(事务未回滚):
  ACC001(张三): 9000.00
  ACC002(李四): 11000.00

问题分析:
  - 默认只回滚RuntimeException和Error
  - 受检异常(Exception)不会触发回滚
  - 需要指定rollbackFor = Exception.class

2. 原因分析

2.1 Spring AOP代理机制

Spring事务基于AOP代理实现,理解代理机制是解决事务失效问题的关键。

参考代码 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动态代理: " + 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("调用链分析:");
            System.out.println("  外部调用流程:");
            System.out.println("    调用方 -> 代理对象.transactionalMethod() -> 目标对象.transactionalMethod()");
            System.out.println();
            System.out.println("  this调用流程:");
            System.out.println("    调用方 -> 代理对象.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. 事务失效原因总结");
        System.out.println("========================================\n");
        
        System.out.println("Spring事务基于AOP代理实现:");
        System.out.println();
        System.out.println("代理模式工作原理:");
        System.out.println("  1. Spring容器启动时,检测到@Transactional注解");
        System.out.println("  2. 创建代理对象包装目标对象");
        System.out.println("  3. 代理对象在方法调用前后添加事务逻辑");
        System.out.println();
        System.out.println("this调用导致失效的原因:");
        System.out.println("  1. 外部调用通过代理对象进入");
        System.out.println("  2. 代理对象开启事务");
        System.out.println("  3. 代理对象调用目标对象的方法");
        System.out.println("  4. 目标对象内部通过this调用其他方法");
        System.out.println("  5. this指向目标对象本身,不是代理对象");
        System.out.println("  6. 绕过了代理,事务逻辑不执行");
        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. 数据库不支持事务:如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() {
        }
    }
}

运行结果:

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

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

Bean的实际类型: com.architect.pitfalls.transactional.analysis.AopProxyAnalysis$DemoService$$SpringCGLIB$$0

是否是AOP代理对象: true

代理类型分析:
  - 是否是JDK动态代理: false
  - 是否是CGLIB代理: true

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

========================================
2. 目标对象 vs 代理对象
========================================

最终目标类: com.architect.pitfalls.transactional.analysis.AopProxyAnalysis$DemoService

调用链分析:
  外部调用流程:
    调用方 -> 代理对象.transactionalMethod() -> 目标对象.transactionalMethod()

  this调用流程:
    调用方 -> 代理对象.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. 事务失效原因总结
========================================

Spring事务基于AOP代理实现:

代理模式工作原理:
  1. Spring容器启动时,检测到@Transactional注解
  2. 创建代理对象包装目标对象
  3. 代理对象在方法调用前后添加事务逻辑

this调用导致失效的原因:
  1. 外部调用通过代理对象进入
  2. 代理对象开启事务
  3. 代理对象调用目标对象的方法
  4. 目标对象内部通过this调用其他方法
  5. this指向目标对象本身,不是代理对象
  6. 绕过了代理,事务逻辑不执行

其他失效场景:
  1. 方法是private:CGLIB无法代理私有方法
  2. 方法是final:CGLIB无法继承final方法
  3. 异常被catch吞掉:事务管理器检测不到异常
  4. 抛出受检异常:默认只回滚RuntimeException
  5. 类没有被Spring管理:@Transactional无效
  6. 数据库不支持事务:如MySQL的MyISAM引擎

2.2 事务失效场景总结

场景原因解决方案
this调用同类方法绕过代理对象自注入/拆分Service/AopContext
方法是privateCGLIB无法代理私有方法改为public
方法是finalCGLIB无法继承final方法去掉final修饰符
异常被catch吞掉事务管理器检测不到异常手动回滚或重新抛出
抛出受检异常默认只回滚RuntimeException指定rollbackFor
类未被Spring管理@Transactional无效添加@Service等注解
数据库不支持事务底层不支持使用支持事务的引擎

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("模拟转账异常");
    }

    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("模拟转账异常");
    }

    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);
    }
}

优点:实现简单,代码改动小
缺点:自注入看起来有些反直觉,可能引起循环依赖警告

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("模拟转账异常");
    }

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

优点:职责单一,符合单一职责原则,易于测试和维护
缺点:需要创建额外的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("模拟转账异常");
    }

    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)

优点:不需要创建额外的类
缺点:代码侵入性强,依赖AOP配置,不推荐在业务代码中使用

3.4 方案对比

方案复杂度推荐度适用场景
自注入低★★★★简单场景
拆分Service中★★★★★推荐,职责清晰
AopContext中★★★框架代码

最佳实践建议:

  1. 优先使用拆分Service方案,职责单一,易于维护
  2. 简单场景可使用自注入方案
  3. 避免在同一个类中通过this调用事务方法
  4. 始终指定 rollbackFor = Exception.class
  5. 事务方法使用public修饰

4. 架构思考

4.1 事务设计原则

单一职责原则:

事务边界设计:

4.2 最佳实践总结

代码层面:

团队规范:

架构设计:

4.3 事务失效检测方法

开发阶段检测:

  1. 单元测试验证事务回滚
  2. 使用@Transactional的readOnly属性测试
  3. 日志级别设置为DEBUG查看事务行为

运行阶段监控:

  1. 监控数据不一致情况
  2. 设置事务异常告警
  3. 定期审计事务日志

通过深入理解Spring事务的AOP代理机制,不仅能避免生产环境的数据不一致问题,更能设计出更加健壮的事务处理方案。在实际项目中,正确使用@Transactional注解至关重要,唯有深入理解其原理和限制,才能构建真正可靠的数据一致性保障。

到此这篇关于Spring的@Transactional失效的几种原因及解决方法的文章就介绍到这了,更多相关Spring @Transactional失效内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文