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 代理来控制事务的开始、提交和回滚,因此只有在通过代理调用方法时,事务才能生效。

相关推荐
bcbnb2 小时前
iOS代码混淆技术深度实践:从基础到高级全面解析
后端
加洛斯3 小时前
SpringSecurity入门篇(2):替换登录页与config配置
前端·后端
用户8356290780513 小时前
Python 实现 Excel 条件格式自动化
后端·python
4***17273 小时前
Spring Boot中Tomcat配置
java
源代码•宸3 小时前
Golang语法进阶(协程池、反射)
开发语言·经验分享·后端·算法·golang·反射·协程池
Chan163 小时前
场景题:CPU 100% 问题怎么排查?
java·数据库·redis·后端·spring
qq_336313934 小时前
java基础-IO流(网络爬虫/工具包生成假数据)
java·爬虫·php
我是谁的程序员4 小时前
iOS 文件管理,在不越狱的前提下管理 iPhone / iPad 文件
后端
v***59834 小时前
springBoot连接远程Redis连接失败(已解决)
spring boot·redis·后端