秒杀订单业务

秒杀订单业务

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

提示:这里可以添加本文要记录的大概内容:
秒杀商品货物时出现的一系列问题


提示:以下是本篇文章正文内容,下面案例可供参考

超卖问题

当我们进行秒杀抢购的时候,很容易出现“超卖问题”
原因是没有实现线程安全,当进行扣减库存的时候,如果另一个线程进入读取到了未被删减的库存,就会导致超卖问题出现。
###悲观锁与乐观锁
悲观锁:认为线程安全问题一定会发生,因此操作数据前,先获取锁,再对数据进行操作,比如synchroized锁,lock锁
乐观锁:认为线程安全问题不一定会发生,因此不加锁,只是在更新数据的时候,判断数据有没有被修改。
这里我们使用乐观锁来解决。
在条件拼接后面操作sql语句,再次查询库存。

  boolean success = seckillVoucherService.update()
                    .setSql("stock = stock - 1")//set stock = stock - 1
                    .eq("voucher_id", voucherId).gt("stock", 0)//where id = ? and stock > 0
                    .update();
            if (!success) {
                //扣减失败
                return Result.fail("扣减失败");
            }

一人一单

我们想实现一个人只能购买一单该怎么实现呢?
基本思路:当这个人想去购买的时候,我们先去查询订单库中有没有包含这个人的id编号

int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
            if (count > 0) {
                //用户下过单了
                return Result.fail("该用户已经购买过了");
            }

同样会涉及到线程安全问题:
如果我们在查询订单库的时候,上一单订单还未写入订单库,一样会造成“超卖问题”
那么我们就需要保证查询订单,写入订单的一致性。

synchronized (userId.toString().intern()) {
            //查询订单
            int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
            if (count > 0) {
                //用户下过单了
                return Result.fail("该用户已经购买过了");
            }
            //5.扣减库存
            boolean success = seckillVoucherService.update()
                    .setSql("stock = stock - 1")//set stock = stock - 1
                    .eq("voucher_id", voucherId).gt("stock", 0)//where id = ? and stock > 0
                    .update();
            if (!success) {
                //扣减失败
                return Result.fail("扣减失败");
            }
            //6.创建订单
            VoucherOrder voucherOrder = new VoucherOrder();
            //6.1订单id
            long orderId = redisIdWorker.nextId("order");
            voucherOrder.setId(orderId);
            //6.2用户id
            voucherOrder.setUserId(userId);
            //6.3代金券id
            voucherOrder.setVoucherId(voucherId);
            save(voucherOrder);
            //7.返回订单id
            return Result.ok(orderId);
    }
}

给整个业务加锁同时在方法上加入@Transactional注解,保证事务的一致性。

注意:锁对象

userId.toString()
* 为什么不能用toString作为锁?
* 因为底层 return new String(buf, UTF16);
* 每回进来都会new一个字符串对象,所以对于相同的用户是锁不住的
* intern()表示从字符串常量池中去找
/
2.锁释放和事务提交之间仍然会存在并发问题
* 如果有人进来查询订单的时候,我们新增的订单可能还没有写入数据库,没有提交
* 因此我们需要在事务提交之后再释放锁

@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    @Resource
    private ISeckillVoucherService seckillVoucherService;

    @Resource
    private RedisIdWorker redisIdWorker;
    @Override
    public Result seckillVocher(Long voucherId) {
        //1.查询优惠券id
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
        //2.判断秒杀是否开始
        if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
            //尚未开始
            return Result.fail("秒杀尚未开始");
        }
        //3.判断秒杀是否结束
        if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
            //已经结束
            return Result.fail("秒杀已结束");
        }
        //4.判断库存是否充足
        if (voucher.getStock() < 1) {
            //库存不足
            return Result.fail("库存不足");
        }
        Long userId = UserHolder.getUser().getId();
        synchronized (userId.toString().intern()) {
            //典型事务失效 this.createVoucherOder(voucherId);
            //this拿到的是当前VoucherOrderServiceImpl对象,而不是代理对象
            //事务想生效其实是对当前类做了动态代理,拿到了代理对象,用代理对象做事务处理
            //此时当前的this,是非代理对象,没有事务功能
            //解决:
            //ps:记得添加一个依赖
            /*<dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            </dependency>*/
            //在启动类上添加暴露注解@EnableAspectJAutoProxy(exposeProxy = true)
            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            return proxy.createVoucherOder(voucherId);
        }
    }

    @Transactional
    public Result createVoucherOder(Long voucherId) {
        //ps:一人一单
        Long userId = UserHolder.getUser().getId();

        /*userId.toString()
        * 为什么不能用toString作为锁?
        * 因为底层 return new String(buf, UTF16);
        * 每回进来都会new一个字符串对象,所以对于相同的用户是锁不住的
        * intern()表示从字符串常量池中去找*/
        /*2.锁释放和事务提交之间仍然会存在并发问题
        * 如果有人进来查询订单的时候,我们新增的订单可能还没有写入数据库,没有提交
        * 因此我们需要在事务提交之后再释放锁*/
        //synchronized (userId.toString().intern()) {
            //查询订单
            int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
            if (count > 0) {
                //用户下过单了
                return Result.fail("该用户已经购买过了");
            }
            //5.扣减库存
            boolean success = seckillVoucherService.update()
                    .setSql("stock = stock - 1")//set stock = stock - 1
                    .eq("voucher_id", voucherId).gt("stock", 0)//where id = ? and stock > 0
                    .update();
            if (!success) {
                //扣减失败
                return Result.fail("扣减失败");
            }
            //6.创建订单
            VoucherOrder voucherOrder = new VoucherOrder();
            //6.1订单id
            long orderId = redisIdWorker.nextId("order");
            voucherOrder.setId(orderId);
            //6.2用户id
            voucherOrder.setUserId(userId);
            //6.3代金券id
            voucherOrder.setVoucherId(voucherId);
            save(voucherOrder);
            //7.返回订单id
            return Result.ok(orderId);
    }
}

典型的事务失效

 Long userId = UserHolder.getUser().getId();
        synchronized (userId.toString().intern()) {
            //典型事务失效 this.createVoucherOder(voucherId);
            //this拿到的是当前VoucherOrderServiceImpl对象,而不是代理对象
            //事务想生效其实是对当前类做了动态代理,拿到了代理对象,用代理对象做事务处理
            //此时当前的this,是非代理对象,没有事务功能
            //解决:
            //ps:记得添加一个依赖
            /*<dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            </dependency>*/
            //在启动类上添加暴露注解@EnableAspectJAutoProxy(exposeProxy = true)
            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            return proxy.createVoucherOder(voucherId);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值