同类调用导致的@Transactional失效

更多事务失效场景:事务失效

同类调用导致的@Transactional失效

同一个类中的内部方法间的调用也是会导致@Transactional失效,当然这也不是说全部失效,具体场景具体分析。下面我们首先看一个案例,在AServiceImpl中,有两个实例方法a(),b() ,并且在testMerge()中进行调用。

public interface AService {
    public void a();
    public void b();
}

@Service()
public class AServiceImpl implements AService{

public void a() {
    standardBakService.save(testAService.buildEntity());
}

public void b() {
    standardBak2Service.save(testBService.buildEntity2());
    throw new RuntimeException("b error");
}
}

@Transactional
public String testMerge() {

    a();

    b();

    return "ok";
}


这里我们看下它的流程是什么:

  • 事务在 testMerge() 方法执行时开启
  • 生成一个代理对象进行调用a(),b()
  • 进入a()中,此时代理对象失效,换用为this进行调用
  • 进入b()中,同理使用this调用,但抛出异常
  • Spring 事务代理失效了(a(),b()方法均使用this执行),但由于事务传播性,@Transactional 仍然会生效

image.png

所以 b()中抛出 RuntimeException,事务会根据默认规则(遇到运行时异常时回滚)进行回滚。

注意这里并不是因为代理对象生效,而是 Spring 会认为 a() 和 b() 依然是在事务的上下文中执行(由事务的传播性),所以testMerge()是认为事务生效的,但不建议这样写的。

但如果是在方法中通过this调用同类的一个事务方法,可能就会出现问题了:

ublic String testMerge() {

    a();

    b();

    return "ok";
}

public void a() {
    standardBakService.save(testAService.buildEntity());
}

@Transactional
public void b() {
    standardBak2Service.save(testBService.buildEntity2());
    throw new RuntimeException("b error");
}

原因就是调用b(),我们还是通过this进行调用的,走到b()方法中,压根就不会开启事务,所以是失效的

同理像下面这种也是,可以先猜下这个案例中有几个事务生效呢?

public interface AService {
    public void a();
    public void b();
}

@Service()
public class AServiceImpl implements AService{
    @Transactional(propagation = Propagation.REQUIRED)
    public void a() {
        this.b();
    }
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void b() {
    }
}

我们在a()中通过this调用b()方法,不是通过代理来调用的,this指向目标对象,因此调用this.b()将不会执行b事务切面,但是a()却是通过代理模式来做的,所以只对a方法进行了事务增强,没有对b方法进行增强

image.png

b中的事务会不会生效?

不会,a的事务会生效,b中不会有事务,因为a中调用b属于内部调用,没有通过代理,所以不会有事务产生。

如果想要b中有事务有效,要如何做?

此处a调用b方法时,通过AOP代理换用this即可执行事务切面,对应事务生效。

具体实现:

  • 引入spring动态代理
<dependency>
<groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>2.3.3.RELEASE</version>
</dependency>
  • 方法调用改成动态代理调用
    • 注入代理对象
    • 使用 ApplicationContext 获取代理对象

注入代理对象:

@Service
public class AServiceImpl implements AService {

    @Autowired
    private AService aService;  // 注入 AService 的代理对象

    @Transactional(propagation = Propagation.REQUIRED)
    public void a() {
        // 使用代理对象调用 b() 方法
        this.aService.b();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void b() {
        // b 的事务将是一个新的事务
    }
}

使用 ApplicationContext 获取代理对象

@Service
public class AServiceImpl implements AService {

    @Autowired
    private ApplicationContext context; // 获取 Spring 上下文

    @Transactional(propagation = Propagation.REQUIRED)
    public void a() {
        // 通过 Spring 上下文获取代理对象
        AService aService = context.getBean(AService.class); 
        aService.b();  // 通过代理对象调用 b()
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void b() {
        // b 的事务将是一个新的事务
    }
}

使用AopContext.currentProxy() ,也可以在当前方法执行上下文中获取代理对象

AopContext.currentProxy() 能够获取当前方法的代理对象,这意味着即使 a() 方法是通过 this 调用的,b() 方法依然能够通过代理对象来执行

@Service
public class AServiceImpl implements AService {

    @Transactional(propagation = Propagation.REQUIRED)
    public void a() {
        // 使用代理调用 b() 方法
        AopContext.currentProxy();  // 获取当前的代理对象
        this.b();  // b() 方法将会通过代理执行,事务传播将生效
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void b() {
        // 这个方法会开启一个新的事务
        System.out.println("In method b, a new transaction is started.");
    }
}

可能报错:

Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.

在 @SpringBootApplication 所在的主类 或者AopConfig配置类中添加配置:

@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true) 

为什么需要 exposeProxy?

Spring AOP 默认使用 JDK 动态代理 或 CGLIB 代理 来处理 AOP 相关的功能。这些代理本质上是对目标对象的包装,但是如果你需要在目标对象中访问代理对象(如 AopContext.currentProxy()),你需要明确告诉 Spring 曝露(Expose)代理对象。

  • exposeProxy = true:表示 Spring 会将当前的代理对象暴露出来,从而允许在当前方法中通过 AopContext.currentProxy() 获取代理对象。
  • proxyTargetClass = true:表示强制使用 CGLIB 代理,即使目标对象实现了接口。如果你的目标对象是通过接口进行代理的(默认情况),可以使用这个配置来强制使用基于类的代理(CGLIB)。

强烈推荐看这篇文章:同类方法调用 @Transactional 失效与解决_transactional 同类调用-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值