前言:
作者简介:我是笑霸final,一名热爱技术的在校学生。
个人主页:个人主页1 || 笑霸final的主页2
系列专栏:《项目专栏》
如果文章知识点有错误的地方,请指正!和大家一起学习,一起进步
如果感觉博主的文章还不错的话,点赞 + 关注 + 收藏
我们使用全局id生成器
我们能不能用redis来完成这个任务?
代码
// 1. 生成时间戳//2.生成序列号//3.拼接并返回
获取时间戳
public static void main(String[] args) {
//获取2022年 1月 1号 0点的秒数
LocalDateTime time = LocalDateTime.of(2022, 1, 1, 0, 0);
long second = time.toEpochSecond(ZoneOffset.UTC);
log.info(Long.toString(second));
}
public Long nexId(String keyPrefix){
// 1. 生成时间戳 31位数字 单位秒
LocalDateTime now = LocalDateTime.now();
long nowSecond = now.toEpochSecond(ZoneOffset.UTC);//当前秒数
Long timestamp=nowSecond- BEG_TIMESTAMP;
//2.生成序列号
//2.1获取当前日期
String yyyyMMdd = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
//2.2自增长
Long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + yyyyMMdd);
//3.拼接并返回
return timestamp<<32 | count;
}
背景:每个商代都可以发布优惠卷,评价卷都可以任意购买,优惠卷需要秒杀抢购!!
数据库
url地址:http://localhost:8081/voucher/seckill
{
"shopId":1,
"title":"100元代金卷",
"subTitle":"周一到周五可用",
"rules":"测试",
"payValue":8000,
"actualValue":10000,
"type":1,
"stock":100,
"beginTime":"2022-01-25T10:09:17",
"endTime":"2023-01-25T10:09:17",
}
一般流程
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
@Resource
private ISeckillVoucherService seckillVoucherService;
@Resource
private RedisIdWorker redisIdWorker;
@Override
@Transactional//两张表 加上事务
public Result seckillVoucher(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("库存不足");
}
//5.扣减库存
boolean success= seckillVoucherService
.update()
.setSql("stock=stock-1")
.eq("voucher_id",voucherId).update();
if (!success){
//库存不足
return Result.fail("库存不足");
}
//6.创建订单
VoucherOrder voucherOrder = new VoucherOrder();
//6.1订单id 用id全局唯一生成器
Long orderId = redisIdWorker.nexId("order");
voucherOrder.setId(orderId);
//6.2用户id
Long userId = UserHolder.getUser().getId();
voucherOrder.setUserId(userId);
//6.3代金卷id
voucherOrder.setVoucherId(voucherId);
save(voucherOrder);
//7.返回id
return Result.ok(orderId);
}
}
解决超卖问题可以加锁:
1.版本号法(运用最广泛的):每次更新数据库的时候按照版本查询,并且要更新版本。
2.CAS
CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。
CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。
CAS法代码:
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
@Resource
private ISeckillVoucherService seckillVoucherService;
@Resource
private RedisIdWorker redisIdWorker;
@Override
@Transactional//两张表 加上事务
public Result seckillVoucher(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("库存不足");
}
//5.扣减库存 乐观锁 在这一刻去判断更新时和查询到的库存是否一致
// boolean success= seckillVoucherService
// .update()
// .setSql("stock=stock-1")
// .eq("voucher_id",voucherId).update();
boolean success= seckillVoucherService
.update()
.setSql("stock=stock-1")
.eq("voucher_id",voucherId)
.eq("stock",voucher.getStock())
.update();
if (!success){
//库存不足
return Result.fail("库存不足");
}
//6.创建订单
VoucherOrder voucherOrder = new VoucherOrder();
//6.1订单id 用id全局唯一生成器
Long orderId = redisIdWorker.nexId("order");
voucherOrder.setId(orderId);
//6.2用户id
Long userId = UserHolder.getUser().getId();
voucherOrder.setUserId(userId);
//6.3代金卷id
voucherOrder.setVoucherId(voucherId);
save(voucherOrder);
//7.返回id
return Result.ok(orderId);
}
}
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
@Resource
private ISeckillVoucherService seckillVoucherService;
@Resource
private RedisIdWorker redisIdWorker;
@Override
public Result seckillVoucher(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();
//intern()在常量池里先去找一样的地址返回
synchronized(userId.toString().intern()) {
VoucherOrderServiceImpl proxy
=(VoucherOrderServiceImpl)AopContext.currentProxy();//代理对象
//因为没有加事务 事务用的代理对象 可能存在事务失效
//没有用代理对象默认是 this.creteVoucherOrder(voucherId)
return proxy.creteVoucherOrder(voucherId);
}
}
@Transactional//两张表 加上事务
public Result creteVoucherOrder(Long voucherId) {
//5一人一单
Long userId = UserHolder.getUser().getId();
//5.1查询订单
Integer count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
//5.2查询是否存在
if (count > 0) {
//存在就不能下单了
return Result.fail("你已经购买过了,只能买一次哟");
}
//6.扣减库存 乐观锁 在这一刻去判断更新时和查询到的库存是否一致
// boolean success= seckillVoucherService
// .update()
// .setSql("stock=stock-1")
// .eq("voucher_id",voucherId).update();
boolean success = seckillVoucherService
.update()
.setSql("stock=stock-1")
.eq("voucher_id", voucherId)
// .eq("stock",voucher.getStock())
.gt("stock", 0)//判断 stock>0可行
.update();
if (!success) {
//库存不足
return Result.fail("库存不足");
}
//7.创建订单
VoucherOrder voucherOrder = new VoucherOrder();
//7.1订单id 用id全局唯一生成器
Long orderId = redisIdWorker.nexId("order");
voucherOrder.setId(orderId);
//7.2用户id
voucherOrder.setUserId(userId);
//7.3代金卷id
voucherOrder.setVoucherId(voucherId);
save(voucherOrder);
//8.返回id
return Result.ok(orderId);
}
}
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
dependency>
同时启动类也加上
@EnableAspectJAutoProxy(exposeProxy = true)//暴露代理对象