@Service
@Transactional//事务控制
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {@ResourceSeckillVoucherServiceImpl seckillVoucherService;@ResourceRedisIdWorker redisIdWorker;@ResourceVoucherOrderMapper voucherOrderMapper;@Overridepublic Result seckillVoucher(Long voucherId) {//1.查询优惠券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("库存不足");}//5.扣减库存boolean success=seckillVoucherService.update().setSql("stock = stock-1")//执行手写SQL.eq("voucher_id",voucherId).update();//Where条件if(!success){//扣减失败return Result.fail("库存不足");}//6.创建订单VoucherOrder voucherOrder = new VoucherOrder();//6.1订单IDlong orderId=redisIdWorker.nextId("order");voucherOrder.setId(orderId);//6.2.用户IDLong userId=UserHolder.getUser().getId();voucherOrder.setUserId(userId);//6.3.代金卷IDvoucherOrder.setVoucherId(voucherId);//6.4.写入数据库save(voucherOrder);//7.返回订单号return Result.ok(orderId);}
}
上述代码会出现超卖问题,
悲观锁:
悲观锁:认为线程安全问题一定会发生,因此在进行数据库操作之前先获取锁,确保线程串行执行 *Synchronized ,
*Lock都属于悲观锁
乐观锁:
线程安全问题不一定发生,因此不加锁,在更新数据时判断有没有其他线程对数据进行了修改
*若没有修改,则认为是安全的,自己更新数据
*若已经被其他线程修改说明已发生安全问题,此时可以重试或异常
实现乐观锁的关键:判断之前查询得到的数据是否被修改过
1、版本号法,添加version字段
2.CAS法把库存代替版本号(太聪明了)
在执行数据库操作时,增加条件stock > 0
boolean success=seckillVoucherService.update().setSql("stock = stock-1")//set stock = stock-1.eq("voucher_id",voucherId)//where voucher_id=?
// .eq("stock",voucher.getStock())//where stock=?成功添加乐观锁,但失败率变大.gt("stock",0)//where stack>0,此处有mysql操作自带的锁保证线程安全 .update();
一人一单:
启动类上加暴露代理对象的注解
@MapperScan("com.hmdp.mapper")
@SpringBootApplication
@EnableAspectJAutoProxy(exposeProxy = true) //暴露代理对象
public class HmDianPingApplication {public static void main(String[] args) {SpringApplication.run(HmDianPingApplication.class, args);}
}
实现一人一单
@Overridepublic Result seckillVoucher(Long voucherId) {//1.查询优惠券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();//定义同步代码块,userId作为锁对象//使用intern方法可以确保所有相同的 userId 都使用同一个字符串对象作为锁//将锁添加在整个方法上,防止先释放锁,后提交业务//synchronized是JVM的本地锁,若为分布式应用,需要分布式锁synchronized (userId.toString().intern()) {//获取代理对象(事务),使得事务得以生效IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();return proxy.createVoucherOrder(voucherId);}}@Transactional//确保本方法中的数据库操作在同一个事务中public Result createVoucherOrder(Long voucherId) {//5.一人一单(需要加锁实现)Long userId = UserHolder.getUser().getId();synchronized (userId.toString().intern()) {//5.1.查询订单int count = query().eq("user_id", userId)//where user_id=?.eq("voucher_id", voucherId).count();//5.2.判断是否已经存在if (count > 0) {return Result.fail("本优惠卷每个用户仅限一单");}//6.扣减库存,添加乐观锁,处理超卖问题boolean success = seckillVoucherService.update().setSql("stock = stock-1")//set stock = stock-1.eq("voucher_id", voucherId)//where voucher_id=?//.eq("stock",voucher.getStock())//where stock=?成功添加乐观锁,但失败率变大.gt("stock", 0)//where stack>0,此处有mysql操作自带的锁保证线程安全.update();if (!success) {//扣减失败return Result.fail("库存不足");}//7.创建订单VoucherOrder voucherOrder = new VoucherOrder();//7.1订单IDlong orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);//7.2.用户IDvoucherOrder.setUserId(userId);//7.3.代金卷IDvoucherOrder.setVoucherId(voucherId);//7.4.写入数据库save(voucherOrder);//8.返回订单号return Result.ok(orderId);}