Spring 事务管理:为什么内部方法调用事务不生效以及如何解决

文章目录

在 Spring 中,事务管理是通过 AOP(面向切面编程) 实现的,它基于 代理模式 来处理事务的开启、提交、回滚等操作。在日常开发中,我们经常会用到 @Transactional 注解来管理方法的事务。然而,很多开发者在使用事务时,遇到一个常见的问题:当一个事务方法内部调用另一个事务方法时,事务是否会生效?

问题背景:

假设我们有一个类 UserService,它有两个方法 methodA()methodB(),并且这两个方法都被标注了 @Transactional 注解。问题是:如果 methodA() 调用 methodB(),事务是否会生效?为什么呢?如果不生效,我们该如何确保事务生效?

Spring 事务传播行为 + 事务失效原因 + 传播行为为什么不用其他模式

一、事务管理和 AOP 代理

Spring 事务管理是如何工作的?

在 Spring 中,事务管理是通过 AOP(面向切面编程) 机制来实现的。具体来说,Spring 会通过 代理模式 来为需要进行事务管理的方法生成代理对象,这个代理对象会在方法执行前后执行一些额外的逻辑(如开启事务、提交事务、回滚事务等)。

  • 当方法上标注了 @Transactional 注解时,Spring 会自动为该方法生成一个 代理对象,并且事务控制(如开启、提交、回滚)是通过该代理对象来管理的。
  • 代理对象会拦截对目标方法的调用,并在目标方法执行前后插入事务的控制逻辑。

AOP 代理的两种方式:

  • JDK 动态代理:基于接口的代理。目标类必须实现接口,Spring 会创建一个接口的代理对象。
  • CGLIB 代理:基于类的代理。即使目标类没有实现接口,Spring 也可以通过字节码技术为目标类创建一个代理对象。

关键点:

  • 代理对象 只会在 外部方法调用时生效。当方法是通过代理对象被调用时,Spring 的事务管理才会插入事务逻辑。
  • 内部方法调用 由于不经过代理对象,因此事务不会生效。

二、问题的根本原因

事务管理不会生效的原因内部方法调用不会经过代理

假设我们有如下代码:

java 复制代码
@Service
public class UserService {

    @Transactional
    public void methodA() {
        System.out.println("Inside methodA, transaction started.");

        // 调用同一类中的 methodB
        this.methodB();  // 事务不会生效

        System.out.println("Inside methodA, transaction committed.");
    }

    @Transactional
    public void methodB() {
        System.out.println("Inside methodB, transaction started.");
        // 模拟数据库操作
        System.out.println("Inside methodB, transaction committed.");
    }
}

在这个例子中,methodA() 调用 methodB(),并且这两个方法都标注了 @Transactional 注解。理论上,我们希望 methodB() 也在 methodA() 的事务中执行。

问题所在:

  • methodA() 调用 this.methodB(),即内部方法调用。
  • Spring 的事务管理是基于代理的 ,事务的控制依赖于 代理对象 。在这种情况下,methodB() 是直接通过 this 调用的,因此事务管理的代理不会生效。
  • 只有外部调用才会触发代理 ,所以即使 methodA()methodB() 上都标注了 @Transactional 注解,methodB() 的事务并不会生效 ,因为它是通过 this 调用的。

三、如何使事务生效

为了确保 methodB() 的事务管理生效,我们需要确保 methodB() 是通过 外部调用 的方式进行调用,这样才能触发事务代理。通常有以下两种方法来解决这个问题:

方法一:将 methodB() 移到不同的类中

methodB() 移到不同的类中,并通过 依赖注入 的方式调用 methodB(),这样可以确保 Spring 会通过代理来管理事务。

java 复制代码
@Service
public class UserServiceA {

    @Autowired
    private UserServiceB userServiceB;  // 依赖注入 UserServiceB

    @Transactional
    public void methodA() {
        System.out.println("Inside methodA, transaction started.");

        // 调用 UserServiceB 中的 methodB,这样会通过代理生效
        userServiceB.methodB();  // 事务会生效

        System.out.println("Inside methodA, transaction committed.");
    }
}

@Service
public class UserServiceB {

    @Transactional
    public void methodB() {
        System.out.println("Inside methodB, transaction started.");
        // 模拟数据库操作
        System.out.println("Inside methodB, transaction committed.");
    }
}

为什么这样可以生效?

  • UserServiceA 中,methodA() 通过 依赖注入 调用了 UserServiceBmethodB() 方法。由于 methodB 是通过代理对象进行调用的,因此事务会在 methodB() 中生效。

方法二:使用 @Transactional 注解的 propagation 属性

另一种方法是利用 @Transactionalpropagation 属性来调整事务的传播行为。默认情况下,事务传播行为是 REQUIRED,即如果方法 A 已经有一个事务,那么方法 B 也会在同一个事务中执行。但这种方式 仅适用于外部方法调用,而在同一类中的方法内部调用,事务管理仍然不会生效。

java 复制代码
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
    // methodB 的事务管理
}

但如果 methodB()同一类内部调用 的,Spring 仍然不会创建事务代理,事务不会生效。

四、总结

  • 为什么事务不生效 :当你在同一个类中调用 @Transactional 标注的方法时,事务 不会生效 ,因为 Spring 使用的事务代理是基于 AOP(代理模式) 的,而 内部调用不会经过代理 。事务代理只会在 外部方法调用 时生效。

  • 如何确保事务生效

    • @Transactional 标注的方法放到不同的类中,并通过 依赖注入 来调用。这样方法的调用会通过代理对象,事务管理会生效。
    • 如果不希望将方法放到不同的类中,可以使用 @Transactional 的传播属性,但这仍然仅适用于外部调用。

Spring 的事务管理依赖于 AOP 代理来控制事务的开始、提交和回滚,因此只有在通过代理调用方法时,事务才能生效。

相关推荐
涡能增压发动积20 小时前
同样的代码循环 10次正常 循环 100次就抛异常?自定义 Comparator 的 bug 让我丢尽颜面
后端
云烟成雨TD20 小时前
Spring AI Alibaba 1.x 系列【6】ReactAgent 同步执行 & 流式执行
java·人工智能·spring
Wenweno0o20 小时前
0基础Go语言Eino框架智能体实战-chatModel
开发语言·后端·golang
于慨20 小时前
Lambda 表达式、方法引用(Method Reference)语法
java·前端·servlet
swg32132120 小时前
Spring Boot 3.X Oauth2 认证服务与资源服务
java·spring boot·后端
tyung20 小时前
一个 main.go 搞定协作白板:你画一笔,全世界都看见
后端·go
gelald20 小时前
SpringBoot - 自动配置原理
java·spring boot·后端
殷紫川20 小时前
深入理解 AQS:从架构到实现,解锁 Java 并发编程的核心密钥
java
一轮弯弯的明月20 小时前
贝尔数求集合划分方案总数
java·笔记·蓝桥杯·学习心得
chenjingming66620 小时前
jmeter线程组设置以及串行和并行设置
java·开发语言·jmeter