什么是超卖?假如只剩下一个库存,却被多个订单买到了,简单理解就是库存不够了还能正常下单。
方案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 "系统异常,请重试";
}
}
}