秒杀订单业务
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
提示:这里可以添加本文要记录的大概内容:
秒杀商品货物时出现的一系列问题
提示:以下是本篇文章正文内容,下面案例可供参考
超卖问题
当我们进行秒杀抢购的时候,很容易出现“超卖问题”
原因是没有实现线程安全,当进行扣减库存的时候,如果另一个线程进入读取到了未被删减的库存,就会导致超卖问题出现。
###悲观锁与乐观锁
悲观锁:认为线程安全问题一定会发生,因此操作数据前,先获取锁,再对数据进行操作,比如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);
1267

被折叠的 条评论
为什么被折叠?



