优惠券秒杀一人一单
优惠券的目的是为了引流,但是目前的情况是一个人可以无限制的抢这个优惠券,因此,代码中应该添加一个用户只能下一单的逻辑。保证一个用户只能抢一张券,则只要保证该用户下的优惠券只要一张,即根据优惠卷id和用户id查询是否已经下过这个订单,如果能够查询到结果,则表明下过该订单,不能再次下单,否则表示没有下过单,进行下单操作。
存在问题:实现一人一单时,也存在线程安全问题,当多个线程携带的同一个用户id和同一个优惠券id时,此时该用户还没获取过该优惠券,则可能出现多线程问题,多个线程通过用户ID和优惠券ID可能查询不到订单,所以会有多个线程能够下单成功。
乐观锁适合更新数据,现在需要时插入数据,所有我们需要使用悲观锁操作。
@Override
public Long seckillVoucher(Long voucherId) {
// 查询秒杀优惠券信息
SeckillVoucher seckillVoucher = seckillVoucherService.getById(voucherId);
//判断秒杀是否开始和结束
LocalDateTime beginTime = seckillVoucher.getBeginTime();
LocalDateTime endTime = seckillVoucher.getEndTime();
//如果当前时间 在开始时间之后 再结束时间之前 则表明秒杀能进行
LocalDateTime localDateTime = LocalDateTime.now();
if ( localDateTime.isBefore(beginTime) || localDateTime.isAfter(endTime) ){
return null;
}
//获取库存量
Integer stock = seckillVoucher.getStock();
if (ObjectUtil.isNull(stock) || ObjectUtil.isNotNull(stock) && stock.intValue() <= 0){
return null;
}
Long voucherOrder = createVoucherOrder(voucherId);
return voucherOrder;
}
@Transactional
public synchronized Long createVoucherOrder(Long voucherId) {
// 根据用户ID和优惠券ID 判断订单是否存在
//获取用户ID
Long id = UserHolder.getUser().getId();
int count = this.count(new LambdaQueryWrapper<VoucherOrder>().eq(VoucherOrder::getUserId, id).eq(VoucherOrder::getVoucherId, voucherId));
// 订单已经存在
if (count > 0){
return null;
}
//扣减库存 将现在库存和之前查询的库存做对比,如果一样,则表明没有人在此中间修改过库存,则认定线程安全,扣除库存
boolean update = seckillVoucherService.update(
new LambdaUpdateWrapper<SeckillVoucher>().setSql("stock = stock - 1").eq(SeckillVoucher::getVoucherId, voucherId)
.gt(SeckillVoucher::getStock, 0)
);
if (!update){
return null;
}
//创建订单
VoucherOrder voucherOrder = new VoucherOrder();
//创建订单ID
long orderId = redisIdWorker.nextId("order");
voucherOrder.setId(orderId);
voucherOrder.setUserId(id);
// 代金券id
voucherOrder.setVoucherId(voucherId);
this.save(voucherOrder);
return orderId;
}
-
对悲观锁进行优化
@Override
public Long seckillVoucher(Long voucherId) {
// 查询秒杀优惠券信息
SeckillVoucher seckillVoucher = seckillVoucherService.getById(voucherId);
//判断秒杀是否开始和结束
LocalDateTime beginTime = seckillVoucher.getBeginTime();
LocalDateTime endTime = seckillVoucher.getEndTime();
//如果当前时间 在开始时间之后 再结束时间之前 则表明秒杀能进行
LocalDateTime localDateTime = LocalDateTime.now();
if ( localDateTime.isBefore(beginTime) || localDateTime.isAfter(endTime) ){
return null;
}
//获取库存量
Integer stock = seckillVoucher.getStock();
if (ObjectUtil.isNull(stock) || ObjectUtil.isNotNull(stock) && stock.intValue() <= 0){
return null;
}
Long id = UserHolder.getUser().getId();
synchronized (id.toString().intern()){
IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
return proxy.createVoucherOrder(id, voucherId);
}
}
@Override
@Transactional
public Long createVoucherOrder(Long userId, Long voucherId) {
// 根据用户ID和优惠券ID 判断订单是否存在
int count = this.count(new LambdaQueryWrapper<VoucherOrder>().eq(VoucherOrder::getUserId, userId).eq(VoucherOrder::getVoucherId, voucherId));
// 订单已经存在
if (count > 0){
return null;
}
//扣减库存 将现在库存和之前查询的库存做对比,如果一样,则表明没有人在此中间修改过库存,则认定线程安全,扣除库存
boolean update = seckillVoucherService.update(
new LambdaUpdateWrapper<SeckillVoucher>().setSql("stock = stock - 1").eq(SeckillVoucher::getVoucherId, voucherId)
.gt(SeckillVoucher::getStock, 0)
);
if (!update){
return null;
}
//创建订单
VoucherOrder voucherOrder = new VoucherOrder();
//创建订单ID
long orderId = redisIdWorker.nextId("order");
voucherOrder.setId(orderId);
voucherOrder.setUserId(userId);
// 代金券id
voucherOrder.setVoucherId(voucherId);
this.save(voucherOrder);
return orderId;
}<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> </dependency>
@EnableAspectJAutoProxy(exposeProxy = true)
@MapperScan("com.hmdp.mapper")
@SpringBootApplication
public class HmDianPingApplication {
public static void main(String[] args) {
SpringApplication.run(HmDianPingApplication.class, args);
}
}
本文由 mdnice 多平台发布