java项目下订单扣库存代码实现(分布式锁)

一、Redis 分布式锁(集群环境使用)

依赖:

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.20.0</version>
</dependency>

1)配置 Redisson

spring:
  redis:
    host: localhost
    port: 6379

2)Service 完整代码

@Service
public class OrderService {

    @Autowired
    private ProductMapper productMapper;
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private RedissonClient redissonClient;

    private static final int MAX_RETRY = 3;

    @Transactional
    public void createOrderWithRedisLock(Long userId, Long productId) {
        // 锁 key:每个商品一把锁
        String lockKey = "lock:product:" + productId;
        RLock lock = redissonClient.getLock(lockKey);

        boolean lockSuccess = false;
        try {
            // 尝试加锁,最多等待 3s,锁 10s 自动释放
            lockSuccess = lock.tryLock(3, 10, TimeUnit.SECONDS);
            if (!lockSuccess) {
                throw new RuntimeException("系统繁忙,请稍后再试");
            }

            // 查库存
            Product product = productMapper.selectById(productId);
            if (product.getStock() <= 0) {
                throw new RuntimeException("库存不足");
            }

            // 扣库存(乐观锁也可以一起用)
            product.setStock(product.getStock() - 1);
            int rows = productMapper.updateById(product);
            if (rows == 0) {
                throw new RuntimeException("并发更新失败");
            }

            // 创建订单
            Order order = new Order();
            order.setUserId(userId);
            order.setProductId(productId);
            orderMapper.insert(order);

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("系统异常");
        } finally {
            if (lockSuccess && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

二、秒杀高并发:Redis 预扣库存

思路:

  • 库存放 Redis
  • decr 原子扣减
  • 扣成功再异步写入数据库

1)初始化库存到 Redis

// 项目启动或后台上架时调用
public void initStockToRedis(Long productId, int stock) {
    redisTemplate.opsForValue().set("stock:" + productId, stock);
}

2)秒杀核心代码(极快)

@Service
public class SeckillService {

    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RabbitTemplate rabbitTemplate; // 或 MQ

    public void secKill(Long userId, Long productId) {
        String stockKey = "stock:" + productId;

        // 原子减 1
        Long remain = redisTemplate.opsForValue().decrement(stockKey);
        if (remain < 0) {
            // 恢复回去,避免负数
            redisTemplate.opsForValue().increment(stockKey);
            throw new RuntimeException("已抢完");
        }

        // 发送 MQ 异步创建订单 + 扣数据库库存
        SecKillMessage msg = new SecKillMessage();
        msg.setUserId(userId);
        msg.setProductId(productId);
        rabbitTemplate.convertAndSend("seckill.exchange", "seckill.key", msg);
    }
}

3)MQ 消费者异步落库

@Component
@RabbitListener(queues = "seckill.queue")
public class SecKillConsumer {

    @Autowired
    private ProductMapper productMapper;
    @Autowired
    private OrderMapper orderMapper;

    @Transactional
    public void consume(SecKillMessage msg) {
        Long productId = msg.getProductId();
        Long userId = msg.getUserId();

        Product product = productMapper.selectById(productId);
        if (product.getStock() <= 0) {
            return;
        }

        // 数据库扣库存
        product.setStock(product.getStock() - 1);
        productMapper.updateById(product);

        // 创建订单
        Order order = new Order();
        order.setUserId(userId);
        order.setProductId(productId);
        orderMapper.insert(order);
    }
}

三、事务失效高频坑(必看)

1)同一个类内调用方法,事务失效 ❌

@Service
public class OrderService {

    // Controller 调用这个
    public void create() {
        doCreateOrder(); // 同类内直接调用 → 不经过代理 → 事务失效
    }

    @Transactional
    public void doCreateOrder() {
        // 扣库存
        // 下单
    }
}

2)正确写法 ✅

方式 A:注入自己

@Autowired
private OrderService self;

public void create() {
    self.doCreateOrder(); // 走代理
}

方式 B:方法直接加 @Transactional

3)try-catch 吞异常,事务不回滚 ❌

@Transactional
public void test() {
    try {
        // 异常
    } catch (Exception e) {
        // 事务不会回滚
    }
}

4)正确 ✅

@Transactional
public void test() {
    try {
    } catch (Exception e) {
        // 手动回滚
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        throw e;
    }
}

四、四种方案怎么选?

  • 普通并发、单服务:乐观锁
  • 集群、多实例:Redisson 分布式锁
  • 秒杀、高并发:Redis 预扣 + MQ 异步
  • 最稳妥生产:Redis 分布式锁 + 乐观锁 + 唯一索引
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值