如何扣钱库存?防止操作超卖

时间:2025-02-24 15:50:34

在 Java 中实现扣减库存并防止超卖,通常可以采用以下几种常见的方法,下面为你详细介绍:

1. 数据库层面加锁


可以使用数据库的事务和锁机制来保证库存扣减的原子性,避免超卖。以 MySQL 为例,在扣减库存时使用行级锁。


示例代码

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class InventoryService {
    private static final String DB_URL = "jdbc:mysql://localhost:3306/your_database";
    private static final String DB_USER = "your_username";
    private static final String DB_PASSWORD = "your_password";

    public boolean deductInventory(int productId, int quantity) {
        Connection conn = null;
        PreparedStatement stmt = null;
        ResultSet rs = null;
        try {
            // 建立数据库连接
            conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
            // 开启事务
            conn.setAutoCommit(false);

            // 查询当前库存并加行级锁
            String selectSql = "SELECT stock FROM inventory WHERE product_id = ? FOR UPDATE";
            stmt = conn.prepareStatement(selectSql);
            stmt.setInt(1, productId);
            rs = stmt.executeQuery();
            if (rs.next()) {
                int currentStock = rs.getInt("stock");
                if (currentStock >= quantity) {
                    // 扣减库存
                    String updateSql = "UPDATE inventory SET stock = stock - ? WHERE product_id = ?";
                    stmt = conn.prepareStatement(updateSql);
                    stmt.setInt(1, quantity);
                    stmt.setInt(2, productId);
                    int rowsAffected = stmt.executeUpdate();
                    if (rowsAffected > 0) {
                        // 提交事务
                        conn.commit();
                        return true;
                    }
                }
            }
            // 回滚事务
            conn.rollback();
        } catch (SQLException e) {
            try {
                if (conn != null) {
                    conn.rollback();
                }
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
            e.printStackTrace();
        } finally {
            try {
                if (rs != null) rs.close();
                if (stmt != null) stmt.close();
                if (conn != null) conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return false;
    }
}

调用示例

public class Main {
    public static void main(String[] args) {
        InventoryService inventoryService = new InventoryService();
        boolean result = inventoryService.deductInventory(1, 10);
        if (result) {
            System.out.println("库存扣减成功");
        } else {
            System.out.println("库存不足,扣减失败");
        }
    }
}

2. 使用 Redis 分布式锁


Redis 是一个高性能的键值存储数据库,可以使用 Redis 的分布式锁来保证在高并发场景下的库存扣减操作的原子性。

示例代码

import redis.clients.jedis.Jedis;

public class RedisInventoryService {
    private static final String REDIS_HOST = "localhost";
    private static final int REDIS_PORT = 6379;
    private static final String LOCK_KEY = "inventory_lock";
    private static final int LOCK_EXPIRE_TIME = 10; // 锁的过期时间,单位:秒

    public boolean deductInventory(String productId, int quantity) {
        Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT);
        try {
            // 获取分布式锁
            String lockResult = jedis.set(LOCK_KEY, "locked", "NX", "EX", LOCK_EXPIRE_TIME);
            if ("OK".equals(lockResult)) {
                try {
                    // 获取当前库存
                    String stockStr = jedis.get(productId);
                    if (stockStr != null) {
                        int currentStock = Integer.parseInt(stockStr);
                        if (currentStock >= quantity) {
                            // 扣减库存
                            jedis.decrBy(productId, quantity);
                            return true;
                        }
                    }
                } finally {
                    // 释放锁
                    jedis.del(LOCK_KEY);
                }
            }
        } finally {
            jedis.close();
        }
        return false;
    }
}

调用示例

public class RedisMain {
    public static void main(String[] args) {
        RedisInventoryService inventoryService = new RedisInventoryService();
        boolean result = inventoryService.deductInventory("product_1", 10);
        if (result) {
            System.out.println("库存扣减成功");
        } else {
            System.out.println("库存不足,扣减失败");
        }
    }
}


3. 使用 Java 并发工具类


在单 JVM 环境下,可以使用 Java 的并发工具类 ReentrantLock 来保证库存扣减的原子性。

示例代码

import java.util.concurrent.locks.ReentrantLock;

public class LocalInventoryService {
    private int stock;
    private final ReentrantLock lock = new ReentrantLock();

    public LocalInventoryService(int initialStock) {
        this.stock = initialStock;
    }

    public boolean deductInventory(int quantity) {
        lock.lock();
        try {
            if (stock >= quantity) {
                stock -= quantity;
                return true;
            }
        } finally {
            lock.unlock();
        }
        return false;
    }
}

调用示例

public class LocalMain {
    public static void main(String[] args) {
        LocalInventoryService inventoryService = new LocalInventoryService(100);
        boolean result = inventoryService.deductInventory(10);
        if (result) {
            System.out.println("库存扣减成功");
        } else {
            System.out.println("库存不足,扣减失败");
        }
    }
}

以上三种方法各有优缺点,你可以根据实际的业务场景和需求选择合适的方法。数据库层面加锁适用于对数据库依赖较大的场景;Redis 分布式锁适用于分布式系统中的高并发场景;Java 并发工具类适用于单 JVM 环境下的并发控制。