黑马点评 事务失效 原因和解决方案

    public Result seckillVoucher(Long voucherId) {       
         synchronized (userID.toString().intern()) {
            //获取代理对象
            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            return proxy.createVoucherOrder(voucherId);
        }
    }

    @Transactional
    public Result createVoucherOrder(Long voucherId){
        Long userID= UserHolder.getUser().getId();    
    }

1. 根本原因 (Cause)

原因概括:发生了 Spring AOP 的“自调用” (Self-Invocation) 问题

在代码中,seckillVoucher 方法内部直接调用了 createVoucherOrder 方法。
虽然 createVoucherOrder 上加了 @Transactional 注解,但因为是类内部调用(等同于 this.createVoucherOrder(...)),绕过了 Spring 的代理对象,导致事务切面逻辑无法执行。

要理解这个根本原因首先得理解Spring的动态代理

步骤 1:启动时扫描 Bean

Spring 启动时:

  • 扫描所有带 @Service@Component 的类
  • 创建这些类的真实对象(target)
OrderService realService = new OrderService(); // 真实对象

步骤 2:检查是否需要增强

Spring 检查 realService 的方法上是否有:

  • @Transactional
  • @Cacheable
  • @Async
  • 自定义 AOP 切面

👉 如果有,不直接把真实对象放进容器,而是:

步骤 3:创建代理对象(关键!)

Spring 根据情况选择代理方式:

条件代理方式
类实现了接口JDK 动态代理(默认)
类没有实现接口CGLIB 代理

 三、JDK 动态代理在 Spring 中的样子(简化版)

假设你的 OrderService 实现了接口:

public interface IOrderService {
    void createOrder();
}

@Service
public class OrderServiceImpl implements IOrderService {
    @Transactional
    public void createOrder() { ... }
}

Spring 内部会生成类似这样的代理(伪代码):

// Spring 动态生成的代理类(运行时创建,你看不到源码)
public class OrderServiceImplProxy implements IOrderService {

    private OrderServiceImpl target; // 真实对象
    private TransactionManager txManager; // Spring 注入的事务管理器

    public void createOrder() {
        // 【前置】检查 @Transactional,开启事务
        if (method.hasAnnotation(Transactional.class)) {
            txManager.begin();
        }

        try {
            // 调用真实对象的方法
            target.createOrder();

            // 【后置】提交事务
            txManager.commit();
        } catch (Exception e) {
            // 【异常】回滚事务
            txManager.rollback();
            throw e;
        }
    }
}

所以只有调用代理对象的createVoucherOrder才能执行到有事务的createVoucherOrder。

2. 底层原理 (Principle)

Spring 的事务管理是基于 AOP (面向切面编程) 实现的,而 AOP 的核心是 动态代理 (Dynamic Proxy)

正常调用流程(事务生效):

当 Controller 或其他类调用 Service 时,Spring 容器注入的不是 VoucherOrderServiceImpl 原生对象,而是一个 代理对象 (Proxy)

  1. 外部调用 -> 代理对象 (Proxy)

  2. 代理对象检测到 @Transactional -> 开启事务 (Start Transaction)

  3. 代理对象调用 -> 目标对象 (Target) 的业务方法

  4. 方法执行完毕 -> 代理对象 提交/回滚事务

代码调用流程(事务失效):
  1. 外部调用 seckillVoucher -> 代理对象(该方法无注解,不开启事务)。

  2. 代理对象调用 -> 目标对象 的 seckillVoucher 方法。

  3. 关键点:在 seckillVoucher 内部执行 createVoucherOrder()。此时使用的是 this 指针。

  4. this 代表的是 目标对象本身,而不是代理对象。

  5. 直接执行普通方法,没有任何 AOP 代码包裹,所以事务完全不存在。

3.解决方案

IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            return proxy.createVoucherOrder(voucherId);

获取当前的代理对象,通过代理对象调用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值