SpringBoot事务失效之this

@Transactional注解的逻辑是通过动态代理来实现的,而生成这个动态代理类分成了两步:
1、向spring容器注册事务相关的切面逻辑
2、根据切面逻辑生成动态代理

此时牢记第二部,是生成动态代理去帮助我们实现事务,那么在什么场景下,导致我们使用事务时,失效呢
很显然,在我们方法中调用事务方法时会导致失效,这就是因为方法调用方法,其指向是this,例如:
image
我们可以看到,在createOrder方法中,调用了事务方法 insertOrderAndReduceStock()方法,那么这个场景就是刚刚所说的事务失效情况之this,其在createOrder中 隐式的调用是 this.insertOrderAndReduceStock(),此时就不是springboot所要的动态代理了,牢记第二点。

解决方法:

  • 通过注入方式,使得orderService去调用insertOrderAndReduceStock方法,但是会导致循环依赖问题,通过注解@Lazy解决
  • 通过AopContext中的currentProxy方法获取代理,在调用insertOrderAndReduceStock方法解决

我的BUG:

点击查看代码
  @Override
    public Result seckillVoucher(Long voucherId) {
        // 1.查询优惠券
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
        // 2.判断秒杀是否开始
        if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
            // 此时 是开始时间大于当前时间,是不允许进来的,说明还没有开始秒杀环境
            return Result.fail("秒杀尚未开始");
        }
        // 3.判断秒杀是否已经结束
        //这行代码是用于判断优惠卷的结束时间是否早于当前时间。也就是说,如果当前时间已经超过了优惠卷结束使用的时间,则返回true
        if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
            // 此时 结束时间
            return Result.fail("秒杀已经结束");
        }
        // 4.判断库存是否充足
        if (voucher.getStock()<1) {
            // 库存不足
            return Result.fail("库存不足");
        }
        Long userId = UserHolder.getUser().getId();
        // 加锁,根据每个用户的id 加锁,锁为userId,这个锁必须确保是唯一的,而toString方法底层是通过new 的方式说名不唯一,通过intern将
        // 字符串放到常量池中,这样每一次一样的字符串地址值就一样了
        synchronized (userId.toString().intern()) {
            // spring在管理事务的时候,需要代理才可以生效,目前知道两种方式,1.自己调用自己  2.通过AopContext中的currentProxy 获取
            // 获取到接口,需要在接口中实现 createVoucherOrder 方法
            IVoucherOrderService proxy =(IVoucherOrderService) AopContext.currentProxy();
            return proxy.createVoucherOrder(voucherId,userId);
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public Result createVoucherOrder(Long voucherId,Long userId) {
            //5. 实现一人一单的功能
            // 5.1 查询订单
            Integer count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
            // 5.2 判断是否存在
            if (count > 0) {
                // 说明 用户有一单,抛出错误
                return Result.fail("该用户已经购买过一次!");
            }

            // 6. 扣减库存
            // update tb_seckill_voucher set stock = stock -1 where voucher_id = #{voucherId} and stock = #{stock}
            // 经过测试发现,大多数线程都会访问失败,这是因为 大多数线程读到了相同的库存量,但是由于其他线程更改了,导致sql不执行
            // 解决方法: 修改条件,将 stock = #{stock} 改为  stock > 0
            boolean success = seckillVoucherService.update().setSql("stock = stock -1")
                    .eq("voucher_id", voucherId)
                    .gt("stock", 0)
                    .update();
            if (!success) {
                // 扣减失败
                return Result.fail("库存不足");
            }
            // 7.创建订单
            VoucherOrder voucherOrder = new VoucherOrder();
            voucherOrder.setVoucherId(voucherId);
            voucherOrder.setUserId(userId);
            long orderId = redisIdWorker.nextId("order");
            voucherOrder.setId(orderId);
            save(voucherOrder);
            // 8.返回订单id
            return Result.ok(orderId);

    }
posted @   自学Java笔记本  阅读(954)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
历史上的今天:
2021-08-12 Filter过滤器
点击右上角即可分享
微信分享提示