项目中如何防止超卖

news2025/4/18 3:24:02

什么是超卖?假如只剩下一个库存,却被多个订单买到了,简单理解就是库存不够了还能正常下单。


方案1:数据库行级锁

1. 实体类

@Data
@TableName("product")
public class Product {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String name;
    
    private Integer stock;
    
    @Version // MyBatis-Plus乐观锁注解
    private Integer version;
}

2. Mapper接口

public interface ProductMapper extends BaseMapper<Product> {
    /​**​
     * 使用悲观锁查询商品
     * @param id 商品ID
     * @return 商品实体
     */
    @Select("SELECT * FROM product WHERE id = #{id} FOR UPDATE")
    Product selectByIdWithLock(Long id);

    /​**​
     * 自定义乐观锁更新方法
     * @param product 商品实体
     * @param oldVersion 旧版本号
     * @return 更新影响行数
     */
    @Update("UPDATE product SET stock = #{stock}, version = version + 1 " +
            "WHERE id = #{id} AND version = #{oldVersion}")
    int updateWithOptimisticLock(Product product, @Param("oldVersion") int oldVersion);
}

3. Service层实现

@Service
@RequiredArgsConstructor
public class ProductService {
    private final ProductMapper productMapper;
    
    /​**​
     * 使用行级锁扣减库存
     * @param productId 商品ID
     * @param quantity 扣减数量
     * @return 操作结果
     */
    @Transactional(rollbackFor = Exception.class)
    public String reduceStock(Long productId, Integer quantity) {
        try {
            // 1. 加行级锁查询商品
            Product product = productMapper.selectByIdWithLock(productId);
            if (product == null) {
                return "商品不存在";
            }
            
            // 2. 检查库存是否充足
            if (product.getStock() < quantity) {
                return "库存不足";
            }
            
            // 3. 扣减库存
            product.setStock(product.getStock() - quantity);
            
            // 4. 更新库存
            int result = productMapper.updateById(product);
            if (result <= 0) {
                throw new RuntimeException("更新库存失败");
            }
            
            return "扣减库存成功";
        } catch (Exception e) {
            // 事务回滚
            throw new RuntimeException("扣减库存异常: " + e.getMessage());
        }
    }
    
    /​**​
     * 使用MyBatis-Plus内置乐观锁机制扣减库存
     * @param productId 商品ID
     * @param quantity 扣减数量
     * @return 操作结果
     */
    @Transactional(rollbackFor = Exception.class)
    public String reduceStockWithOptimisticLock(Long productId, Integer quantity) {
        // 1. 查询商品(不加锁)
        Product product = productMapper.selectById(productId);
        if (product == null) {
            return "商品不存在";
        }
        
        // 2. 检查库存
        if (product.getStock() < quantity) {
            return "库存不足";
        }
        
        // 3. 扣减库存
        product.setStock(product.getStock() - quantity);
        
        // 4. 乐观锁更新
        int result = productMapper.updateById(product);
        if (result <= 0) {
            // 版本号不一致,说明数据已被修改
            return "操作失败,请重试";
        }
        
        return "扣减库存成功";
    }

    /​**​
     * 手动实现乐观锁扣减库存(带重试机制)
     * @param productId 商品ID
     * @param quantity 扣减数量
     * @return 操作结果
     */
    @Transactional(rollbackFor = Exception.class)
    public String reduceStockManual(Long productId, int quantity) {
        // 1. 查询商品
        Product product = productMapper.selectById(productId);
        if (product == null) {
           return "商品不存在";
        }
            
        // 2. 检查库存是否充足
        if (product.getStock() < quantity) {
           return "库存不足";
        }
            
        // 3. 扣减库存
        product.setStock(product.getStock() - quantity);
            
        // 4. 手动乐观锁更新
        int result = productMapper.updateWithOptimisticLock(
        product, product.getVersion());
            
        if (result > 0) {
           return "扣减库存成功";
        }
            
        
        return "操作失败,请重试";
    }
}

方案2:分布式锁

1. 实体类

@Data
@TableName("product")
public class Product {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String name;
    
    private Integer stock;
}

2. Mapper接口

public interface ProductMapper extends BaseMapper<Product> {
    // 使用MyBatis-Plus自带方法
    
    /​**​
     * 自定义扣减库存方法
     * @param productId 商品ID
     * @param quantity 扣减数量
     * @return 影响行数
     */
    @Update("UPDATE product SET stock = stock - #{quantity} WHERE id = #{productId} AND stock >= #{quantity}")
    int reduceStock(@Param("productId") Long productId, @Param("quantity") int quantity);
}

3. Service层实现

@Service
@RequiredArgsConstructor
public class ProductService {
    private final ProductMapper productMapper;
    private final RedissonClient redissonClient;
    
    
    /​**​
     * Redisson分布式锁
     */
    public String reduceStockWithLock(Long productId, int quantity) {
        // 1. 创建分布式锁key
        String lockKey = "product:lock:" + productId;
        RLock lock = redissonClient.getLock(lockKey);
        
        try {
            // 2. 尝试获取锁(等待5秒,锁自动释放时间30秒)
            boolean locked = lock.tryLock(5, 30, TimeUnit.SECONDS);
            if (!locked) {
                return "系统繁忙,请稍后再试";
            }
            
            try {
                // 3. 查询商品
                Product product = productMapper.selectById(productId);
                if (product == null) {
                    return "商品不存在";
                }
                
                // 4. 检查库存是否充足
                if (product.getStock() < quantity) {
                    return "库存不足";
                }
                
                // 5. 扣减库存
                int result = productMapper.reduceStock(productId, quantity);
                if (result <= 0) {
                    return "扣减失败,请重试";
                }
                
                return "扣减成功";
            } finally {
                // 6. 释放锁
                // lock.isLocked()检查锁是否仍然被持有(未被释放)
                // lock.isHeldByCurrentThread()检查当前线程是否持有该锁
                if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return "系统异常,请重试";
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2331227.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

重回全面发展亲自操刀

项目场景&#xff1a; 今年工作变动&#xff0c;优化后在一家做国有项目的私人公司安顿下来了。公司环境不如以前&#xff0c;但是好在瑞欣依然可以每天方便的买到。人文氛围挺好&#xff0c;就是工时感觉有点紧&#xff0c;可能长期从事产品迭代开发&#xff0c;一下子转变做项…

3D珠宝渲染用什么软件比较好?渲染100邀请码1a12

印度珠宝商 Mohar Fine Jewels 和英国宝石商 Gemfields 在今年推出了合作珠宝系列——「Emeralds in Full Bloom」&#xff0c;它的灵感源自花草绽放的春季田野&#xff0c;共有 39 件作品&#xff0c;下面这个以植物为主题的开口手镯就是其中一件。 在数字时代&#xff0c;像这…

【数据结构】邻接矩阵完全指南:原理、实现与稠密图优化技巧​

邻接矩阵 导读一、图的存储结构1.1 分类 二、邻接矩阵法2.1 邻接矩阵2.2 邻接矩阵存储网 三、邻接矩阵的存储结构四、算法评价4.1 时间复杂度4.2 空间复杂度 五、邻接矩阵的特点5.1 特点1解析5.2 特点2解析5.3 特点3解析5.4 特点4解析5.5 特点5解析5.6 特点6解析 结语 导读 大…

【嵌入式-stm32电位器控制以及旋转编码器控制LED亮暗】

嵌入式-stm32电位器控制LED亮暗 任务1代码1Key.cKey.hTimer.cTimer.hPWM.cPWM.hmain.c 实验现象1任务2代码2Key.cKey.hmain.c 实验现象2问题与解决总结 源码框架取自江协科技&#xff0c;在此基础上做扩展开发。 任务1 本文主要介绍利用stm32f103C8T6实现电位器控制PWM的占空比…

Uniapp 集成极光推送(JPush)完整指南

文章目录 前言一、准备工作1. 注册极光开发者账号2. 创建应用3. Uniapp项目准备 二、集成极光推送插件方法一&#xff1a;使用UniPush&#xff08;推荐&#xff09;方法二&#xff1a;手动集成极光推送SDK 三、配置原生平台参数四、核心功能实现1. 获取RegistrationID2. 设置别…

2025年常见渗透测试面试题-sql(题目+回答)

网络安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 SQLi 一、发现test.jsp?cid150 注入点的5种WebShell获取思路 1. 文件写入攻击 2. 日志文件劫持 3.…

【RabbitMQ】队列模型

1.概述 RabbitMQ作为消息队列&#xff0c;有6种队列模型&#xff0c;分别在不同的场景进行使用&#xff0c;分别是Hello World&#xff0c;Work queues&#xff0c;Publish/Subscribe&#xff0c;Routing&#xff0c;Topics&#xff0c;RPC。 下面就分别对几个模型进行讲述。…

StarRocks 助力首汽约车精细化运营

作者&#xff1a;任智红&#xff0c;首汽约车大数据负责人 更多交流&#xff0c;联系我们&#xff1a;https://wx.focussend.com/weComLink/mobileQrCodeLink/334%201%202/ffbe5 导读&#xff1a; 本文整理自首汽约车大数据负责人任智红在 StarRocks 年度峰会上的演讲&#xf…

痉挛性斜颈康复助力:饮食调养指南

痉挛性斜颈患者除了积极治疗&#xff0c;合理饮食也能辅助缓解症状&#xff0c;提升生活质量。其健康饮食可从以下方面着手&#xff1a; 高蛋白质食物助力肌肉修复 痉挛性斜颈会导致颈部肌肉异常收缩&#xff0c;消耗较多能量&#xff0c;蛋白质有助于肌肉的修复与维持。日常可…

mysql镜像创建docker容器,及其可能遇到的问题

前提&#xff0c;已经弄好基本的docker服务了。 一、基本流程 1、目录准备 我自己的资料喜欢放在 /data 目录下&#xff0c;所以老规矩&#xff1a; 先进入 /data 目录&#xff1a; cd /data 创建 mysql 目录并进入&#xff1a; mkdir mysql cd mysql 2、镜像查找 docke…

JavaEE——线程的状态

目录 前言1. NEW2. TERMINATED3. RUNNABLE4. 三种阻塞状态总结 前言 本篇文章来讲解线程的几种状态。在Java中&#xff0c;线程的状态是一个枚举类型&#xff0c;Thread.State。其中一共分为了六个状态。分别为&#xff1a;NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING, TERMI…

RuntimeError: Error(s) in loading state_dict for ChartParser

一 bug错误 最近使用千问大模型有一个bug&#xff0c;报错信息如下 raise RuntimeError(Error(s) in loading state_dict for {}:\n\t{}.format( RuntimeError: Error(s) in loading state_dict for ChartParser:Unexpected key(s) in state_dict: "pretrained_model.em…

2025 年安徽交安安全员考试:利用记忆宫殿强化记忆​

安徽考生在面对交安安全员考试繁杂的知识点时&#xff0c;记忆宫殿是强大的记忆工具。选择一个熟悉且空间结构清晰的场所作为记忆宫殿&#xff0c;如自己居住的房屋。将房屋的不同区域&#xff0c;如客厅、卧室、厨房等&#xff0c;分别对应不同知识板块&#xff0c;像客厅对应…

安全编码课程 实验6 整数安全

实验项目 实现安全计数器&#xff1a;实现 Counter 结构&#xff0c;确保计数范围为 0~100。 实验要求&#xff1a; 1、使用 struct 封装计数值value&#xff1b; 2、计数器初值为 0&#xff1b; 3、increment() 方法增加计数&#xff0c;但不能超过 100&#xff1b; 4、decrem…

解决上传PDF、视频、音频等格式文件到FTP站点时报错“将文件复制到FTP服务器时发生错误。请检查是否有权限将文件放到该服务器上”问题

一、问题描述 可以将文本文件(.txt格式),图像文件(.jpg、.png等格式)上传到我们的FTP服务器上;但是上传一些PDF文件、视频等文件时就会报错“ 将文件复制到FTP服务器时发生错误。请检查是否有权限将文件放到该服务器上。 详细信息: 200 Type set to l. 227 Entering Pas…

【Linux操作系统】:信号

Linux操作系统下的信号 一、引言 首先我们可以简单理解一下信号的概念&#xff0c;信号&#xff0c;顾名思义&#xff0c;就是我们操作系统发送给进程的消息。举个简单的例子&#xff0c;我们在写C/C程序的时候&#xff0c;当执行a / 0类似的操作的时候&#xff0c;程序直接就挂…

经典频域分析法(Bode图、Nyquist判据) —— 理论、案例与交互式 GUI 实现

目录 经典频域分析法(Bode图、Nyquist判据) —— 理论、案例与交互式 GUI 实现一、引言二、经典频域分析方法的基本原理2.1 Bode 图分析2.2 Nyquist 判据三、数学建模与公式推导3.1 一阶系统的频域响应3.2 多极系统的 Bode 图绘制3.3 Nyquist 判据的数学描述四、经典频域分析…

使用scoop一键下载jdk和实现版本切换

安装 在 PowerShell 中输入下面内容&#xff0c;保证允许本地脚本的执行&#xff1a; set-executionpolicy remotesigned -scope currentuser然后执行下面的命令安装 Scoop&#xff1a; iwr -useb get.scoop.sh | iex国内用户可以使用镜像源安装&#xff1a;powershell iwr -us…

对状态模式的理解

对状态模式的理解 一、场景二、不采用状态模式1、代码2、缺点 三、采用状态模式1、代码1.1 状态类1.2 上下文&#xff08;这里指&#xff1a;媒体播放器&#xff09;1.3 客户端 2、优点 一、场景 同一个东西&#xff08;例如&#xff1a;媒体播放器&#xff09;&#xff0c;有一…

vue2(webpack)集成electron和 electron 打包

前言 之前发过一篇vue集成electron的文章&#xff0c;但是用vue3vite实现的&#xff0c;在vue2webpack工程可能不适用&#xff0c;所以这篇文章就主要介绍vue2webpack集成electron方法 创建项目 vue create vue-electron-demo目录架构 vue-electron-demo/ ├── src/ …