超卖问题的基础上,在创建订单时,进行是否存在的检查,并将从该检查到创建完成的过程进行对单个用户的加锁。

实现

创建订单

@Transactional  
public Result createVoucherOrder(long voucherId) {  
    long userId = UserHolder.getUser().getId();  
    // 1.一人一单  
    int count = query().eq("user_id", userId)  
            .eq("voucher_id", voucherId)  
            .count();  
    if (count > 0) {  
        return Result.fail("用户已经购买过一次!");  
    }  
  
    // 2.更新库存  
    boolean success = seckillVoucherService.update()  
            .setSql("stock = stock - 1")  
            .eq("voucher_id", voucherId)  
            .gt("stock", 0)  
            .update();  
    if (!success) {  
        return Result.fail("库存不足!");  
    }  
    // 3.创建订单  
    VoucherOrder voucherOrder = new VoucherOrder();  
    long orderId = idGenerator.generate("order");  
    voucherOrder.setId(orderId);  
    voucherOrder.setVoucherId(voucherId);  
    voucherOrder.setUserId(UserHolder.getUser().getId());  
    save(voucherOrder);  
    return Result.ok(orderId);  
}

注意

因为createVoucherOrder方法存在多个写操作,因此需要使用事务,这里需要注意的是锁需要把整个事务包起来,否则如果先释放锁后回滚事务,可能造成其他线程获取到锁然后修改数据结果又被回滚导致数据不一致的问题。

加锁

如果直接在对象中直接调用类中的createVoucher方法,而不是使用注入的代理对象调用事务就不会生效,因此需要使用AopContext.currentProxy()来获取当前类的代理对象。

synchronized (UserHolder.getUser().getId().toString().intern()) {  
    IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();  
    return proxy.createVoucherOrder(voucherId);  
}

synchronized只适合单机情况下使用,在分布式集群模式下需要使用分布式锁

// 5.获取分布式锁  
long userId = UserHolder.getUser().getId();  
String key = "order:" + userId;  
RLock lock = redissonClient.getLock(key);  
if (!lock.tryLock()) {  
    return Result.fail("不允许重复下单!");  
}  
// 6.创建优惠券订单  
try {
	IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();  
	return proxy.createVoucherOrder(voucherId);  
} finally {  
    lock.unlock();  
}