跳至内容
返回

Spring自调用导致注解失效

发布于:  at  10:00 上午

解决方法的核心在于理解Spring事务管理的实现原理------代理模式(Proxy)​

首先拆解一下这个问题和解决方案。

1. 什么是”自调用”?

假设你有一个 UserService类:

@Service
public class UserService {

    public void a() {
        // ... 一些业务逻辑
        this.b(); // 自调用:在同一个类中,通过`this`调用方法b()
        // ... 其他逻辑
    }

    @Transactional // 声明了事务
    public void b() {
        // ... 需要对数据库进行操作,期望在事务中执行
        userRepository.save(...);
    }
}

当你从外部调用 userService.a()时,你期望方法 b()中的数据库操作在一个事务中运行,但实际上它没有。事务注解 @Transactional失效了。

2. 为什么绕过了代理?

我们要先知道Spring是如何实现事务管理的:

  1. Spring使用代理(Proxy)​​:当你给一个Bean的方法加上 @Transactional注解后,Spring在启动时会为这个Bean创建一个代理对象​(可以理解为这个Bean的”管家”或”替身”),并把这个代理对象放入Spring容器中。

  2. 外部调用走代理​:当其他组件(如Controller)通过 @Autowired注入 UserService并调用其方法时,它实际上拿到的是那个代理对象,而不是原始的 UserService对象。

    • 你调用 proxy.a()

    • 代理先做事务管理(如开启事务)

    • 代理再去调用原始对象a()方法

    • 最后代理提交或回滚事务

  3. 自调用不走代理​:但在上面的例子中,你在方法 a()内部使用的是 this.b()。这里的 this指的是原始的 UserService对象本身,而不是它的代理对象。

    • 流程变成了:Controller-> 代理.a()-> 原始对象.a()-> 原始对象.b()

    • 调用 b()方法时,完全绕过了代理管家。管家根本没有机会为 b()方法开启事务。

比喻的说法便于理解:​

3. 首选解决方案:“放到另一个Service类中”

解决方案 a的代码示例:

// ServiceA.java
@Service
public class ServiceA {

    @Autowired
    private ServiceB serviceB; // 注入另一个Service

    public void a() {
        // ... 一些业务逻辑
        serviceB.b(); // 通过代理调用另一个Service的事务方法
        // ... 其他逻辑
    }
}

// ServiceB.java
@Service
public class ServiceB {

    @Transactional // 声明了事务
    public void b() {
        // ... 需要对数据库进行操作,期望在事务中执行
        userRepository.save(...);
    }
}

为什么这样就能解决问题?​

  1. 调用路径变化​:现在,ServiceA中的方法 a()是通过 @Autowired注入的 serviceB来调用方法 b()的。

  2. 注入的是代理​:Spring注入的 serviceB并不是原始的 ServiceB对象,而是Spring为 ServiceB创建的代理对象

  3. 代理生效​:所以,调用 serviceB.b()的流程是:

    • ServiceA.a()-> ServiceB的代理.b()

    • 代理先开启事务

    • 代理再调用原始ServiceB对象b()方法

    • 代理根据执行结果提交或回滚事务

这样一来,事务管理就完全正常了。​

其他解决方案

总结:​

当你发现因为自调用导致事务、缓存、异步等注解失效时,第一反应就应该是:“我应该把这个方法抽到一个新的Bean里


建议修改
在以下平台分享此文章:

上一篇
项目日记第一天:Redis快速入门
下一篇
大象席地而坐