当前位置:首页 > Java

java商品如何扣减

2026-03-21 14:40:15Java

商品库存扣减的基本逻辑

商品库存扣减是电商系统中常见的操作,核心目标是保证在高并发场景下数据的一致性。Java中实现库存扣减需要考虑原子性、并发控制和异常处理。

数据库乐观锁实现

通过版本号或时间戳实现乐观锁,适合并发量适中的场景:

// SQL示例
UPDATE product SET stock = stock - #{quantity}, version = version + 1 
WHERE id = #{productId} AND version = #{version} AND stock >= #{quantity}

需要在Service层进行重试逻辑:

java商品如何扣减

@Transactional
public boolean deductStock(Long productId, int quantity) {
    int retryTimes = 3;
    while (retryTimes-- > 0) {
        Product product = productMapper.selectById(productId);
        if (product.getStock() < quantity) return false;

        int affected = productMapper.deductStock(productId, quantity, product.getVersion());
        if (affected > 0) return true;
    }
    return false;
}

Redis原子操作方案

利用Redis的原子特性实现高性能扣减:

// Lua脚本保证原子性
String luaScript = "if redis.call('exists', KEYS[1]) == 1 then " +
                   "local stock = tonumber(redis.call('get', KEYS[1])) " +
                   "if stock >= tonumber(ARGV[1]) then " +
                   "return redis.call('incrby', KEYS[1], -tonumber(ARGV[1])) " +
                   "end " +
                   "return -1 " +
                   "end " +
                   "return -2";

Long result = redisTemplate.execute(
    new DefaultRedisScript<>(luaScript, Long.class),
    Collections.singletonList("stock:" + productId),
    String.valueOf(quantity)
);

分布式锁解决方案

使用Redisson实现分布式锁:

java商品如何扣减

public boolean deductStockWithLock(Long productId, int quantity) {
    RLock lock = redissonClient.getLock("product:" + productId);
    try {
        lock.lock();
        Product product = productMapper.selectById(productId);
        if (product.getStock() >= quantity) {
            product.setStock(product.getStock() - quantity);
            productMapper.updateById(product);
            return true;
        }
        return false;
    } finally {
        lock.unlock();
    }
}

消息队列异步处理

应对超高并发场景可采用异步扣减模式:

@KafkaListener(topics = "stock-deduction")
public void handleStockDeduction(StockMessage message) {
    try {
        deductStock(message.getProductId(), message.getQuantity());
    } catch (Exception e) {
        // 记录日志并加入重试队列
    }
}

异常处理与补偿机制

需要实现补偿逻辑处理失败场景:

@TransactionalEventListener(phase = AFTER_COMMIT)
public void onOrderCreated(OrderCreatedEvent event) {
    try {
        if(!stockService.deductStock(event.getProductId(), event.getQuantity())) {
            // 触发库存不足补偿流程
        }
    } catch (Exception e) {
        // 记录异常并触发补偿
    }
}

数据一致性验证

定期核对Redis与数据库库存:

@Scheduled(cron = "0 0 3 * * ?")
public void stockSyncTask() {
    List<Product> products = productMapper.selectList(null);
    products.forEach(p -> {
        Integer redisStock = redisTemplate.opsForValue().get("stock:" + p.getId());
        if(redisStock != null && !redisStock.equals(p.getStock())) {
            // 触发库存校正
        }
    });
}

实际实现时应根据业务场景选择合适方案,超高并发建议采用Redis+Lua+异步消息的组合方案,普通场景可直接使用数据库乐观锁。所有方案都需配合完善的监控和报警机制。

标签: 商品java
分享给朋友:

相关文章

如何查看java版本

如何查看java版本

查看 Java 版本的方法 通过命令行工具 打开终端(Windows 为命令提示符或 PowerShell,macOS/Linux 为 Terminal),输入以下命令并回车: java -ve…

java如何读取文件

java如何读取文件

读取文件的常用方法 Java提供了多种读取文件的方式,适用于不同场景和需求。以下是几种常见的方法: 使用FileInputStream和BufferedInputStream读取二进制文件 try…

java如何调用接口

java如何调用接口

调用接口的基本方法 在Java中调用接口通常涉及实现接口或使用接口引用对象。以下是几种常见场景的示例: 定义接口 public interface MyInterface { void d…

java如何输入字符串

java如何输入字符串

使用 Scanner 类 在 Java 中,可以通过 java.util.Scanner 类来输入字符串。以下是一个示例代码: import java.util.Scanner; public c…

如何编写java

如何编写java

编写Java程序的基本步骤 安装Java开发工具包(JDK),确保系统环境变量配置正确。可以从Oracle官网下载最新版本的JDK并进行安装。 选择一个合适的集成开发环境(IDE),如Eclipse…

java如何运行程序

java如何运行程序

运行Java程序的步骤 安装Java开发工具包(JDK) 确保系统已安装JDK,可通过命令行输入java -version和javac -version验证。若未安装,需从Oracle官网下载并配置环…