@Service
@Transactional//事务控制
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
@Resource
SeckillVoucherServiceImpl seckillVoucherService;
@Resource
RedisIdWorker redisIdWorker;
@Resource
VoucherOrderMapper voucherOrderMapper;
@Override
public 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订单ID
long orderId=redisIdWorker.nextId("order");
voucherOrder.setId(orderId);
//6.2.用户ID
Long userId=UserHolder.getUser().getId();
voucherOrder.setUserId(userId);
//6.3.代金卷ID
voucherOrder.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);
}
}
实现一人一单
@Override
public 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订单ID
long orderId = redisIdWorker.nextId("order");
voucherOrder.setId(orderId);
//7.2.用户ID
voucherOrder.setUserId(userId);
//7.3.代金卷ID
voucherOrder.setVoucherId(voucherId);
//7.4.写入数据库
save(voucherOrder);
//8.返回订单号
return Result.ok(orderId);
}