关键词

Spring Boot2+JPA之悲观锁和乐观锁实战教程

下面我就为您讲解Spring Boot2 + JPA悲观锁和乐观锁实战教程的完整攻略。

1. 悲观锁实战

1.1 悲观锁的概念

悲观锁是指,当在执行某一操作时,认为别的并发操作会对其产生影响,因此在执行前进行加锁,使得其他并发操作无法操作,直到该操作完成释放锁。

1.2 悲观锁的实现

在JPA中,实现悲观锁可以通过 @Lock 注解来实现。具体实现方法如下:

/**
 * 根据id查询订单
 * @param id 订单id
 * @return 订单信息
 */
@Transactional(readOnly = true)
@Lock(LockModeType.PESSIMISTIC_WRITE)    // 悲观写锁
OrderEntity findById(Integer id);

在查询订单时,使用了 @Lock(LockModeType.PESSIMISTIC_WRITE) 注解,并传入参数 LockModeType.PESSIMISTIC_WRITE,表示使用悲观写锁。

1.3 悲观锁实例1

下面我们以一个简单的订单支付接口(为了订单操作的幂等性,只进行部分支付)为例来实现悲观锁。

首先,在 OrderEntity 实体类中增加一个 version 字段,用于实现乐观锁。

@Entity
@Table(name = "t_order")
public class OrderEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    ...

    @Version
    private Integer version;    // 版本号

    ...
}

然后,在订单支付接口实现中,使用 findByIdAndVersion 方法查询订单时,加上 @Lock(LockModeType.PESSIMISTIC_WRITE) 注解,表示使用悲观写锁。

@Service
public class OrderServiceImpl implements OrderService {

    ...

    @Transactional(rollbackFor = Exception.class)
    public void pay(Integer id, BigDecimal amount) {
        OrderEntity order = orderRepository.findByIdAndVersion(id, 0, LockModeType.PESSIMISTIC_WRITE)
                .orElseThrow(() -> new BusinessException(ErrorCode.ORDER_NOT_FOUND));

        order.setAmountPaid(order.getAmountPaid().add(amount));
        orderRepository.save(order);
    }

    ...
}

在上述代码中,我们使用 findByIdAndVersion 方法查询订单时,加上 @Lock(LockModeType.PESSIMISTIC_WRITE) 注解,并传入参数 LockModeType.PESSIMISTIC_WRITE,表示使用悲观写锁。这样保证在进行部分支付时,其他线程无法对该订单进行支付操作。

1.4 悲观锁实例2

接下来,我们再以一个更复杂的需求为例,来实现悲观锁。假设我们有一张商品表,每当有用户购买该商品时,库存数量会减1。但是,为了避免超卖现象,我们需要在减库存前查询库存数量并使用悲观写锁进行加锁,以确保操作的正确性。

首先,在 ProductEntity 实体类中增加一个 version 字段,用于实现乐观锁。

@Entity
@Table(name = "t_product")
public class ProductEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    ...

    @Version
    private Integer version;    // 版本号

    ...
}

然后,在商品服务实现中,使用 findById 方法查询商品时,加上 @Lock(LockModeType.PESSIMISTIC_WRITE) 注解,表示使用悲观写锁。

@Service
public class ProductServiceImpl implements ProductService {

    ...

    @Transactional(rollbackFor = Exception.class)
    public void purchase(Integer productId) {
        ProductEntity product = productRepository.findById(productId)
                .orElseThrow(() -> new BusinessException(ErrorCode.PRODUCT_NOT_FOUND));

        synchronized (ProductServiceImpl.class) {
            if (product.getStock() > 0) {
                product.setStock(product.getStock() - 1);
                productRepository.save(product);
            } else {
                throw new BusinessException(ErrorCode.STOCK_NOT_ENOUGH);
            }
        }
    }

    ...
}

在上述代码中,我们使用 findById 方法查询商品时,加上 @Lock(LockModeType.PESSIMISTIC_WRITE) 注解,并传入参数 LockModeType.PESSIMISTIC_WRITE,表示使用悲观写锁。然后在操作库存前使用 synchronized 关键字加锁,以确保操作的正确性。

2. 乐观锁实战

2.1 乐观锁的概念

乐观锁是指,当在执行某一操作时,认为别的并发操作不会对其产生影响,因此在执行前不进行加锁,而是在更新操作时判断该记录的版本是否已被其他并发操作修改,如果该记录的版本未被修改,则更新成功;否则更新失败,并重新尝试更新。

2.2 乐观锁的实现

在JPA中,实现乐观锁可以通过 @Version 注解来实现。当更新实体时,如果版本号发生变化,则更新失败并抛出 OptimisticLockException 异常。

2.3 乐观锁实例1

我们仍然以订单为例,使用 @Version 注解实现乐观锁。

首先,在 OrderEntity 实体类中增加一个 version 字段,用于实现乐观锁。

@Entity
@Table(name = "t_order")
public class OrderEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    ...

    @Version
    private Integer version;    // 版本号

    ...
}

然后,在订单支付接口实现中,使用 findById 方法查询订单时,加上版本号判断,并在更新订单时增加版本号。

@Service
public class OrderServiceImpl implements OrderService {

    ...

    @Transactional(rollbackFor = Exception.class)
    public void pay(Integer id, BigDecimal amount) {
        OrderEntity order = orderRepository.findById(id)
                .orElseThrow(() -> new BusinessException(ErrorCode.ORDER_NOT_FOUND));

        if (order.getVersion() == 0) {
            order.setVersion(1);
        }

        order.setAmountPaid(order.getAmountPaid().add(amount));
        order.setVersion(order.getVersion() + 1);
        orderRepository.save(order);
    }

    ...
}

在上述代码中,我们在查询订单时,判断订单的版本号是否为0,如果是,则设置版本号为1;否则在更新订单时,将版本号加1。

2.4 乐观锁实例2

再以商品为例,使用 @Version 注解实现乐观锁。

首先,在 ProductEntity 实体类中增加一个 version 字段,用于实现乐观锁。

@Entity
@Table(name = "t_product")
public class ProductEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    ...

    @Version
    private Integer version;    // 版本号

    ...
}

然后,在商品服务实现中,使用 findById 方法查询商品时,在更新商品时增加版本号,并在更新时判断版本号是否正确。

@Service
public class ProductServiceImpl implements ProductService {

    ...

    @Transactional(rollbackFor = Exception.class)
    public void purchase(Integer productId) {
        ProductEntity product = productRepository.findById(productId)
                .orElseThrow(() -> new BusinessException(ErrorCode.PRODUCT_NOT_FOUND));

        if (product.getStock() > 0) {
            product.setStock(product.getStock() - 1);
            product.setVersion(product.getVersion() + 1);
            productRepository.save(product);
        } else {
            throw new BusinessException(ErrorCode.STOCK_NOT_ENOUGH);
        }
    }

    ...
}

在上述代码中,我们在更新商品时,将版本号加1,并在更新时判断版本号是否正确,如果版本号不正确,则更新失败并抛出 OptimisticLockException 异常。

这就是Spring Boot2+JPA悲观锁和乐观锁实战教程的完整攻略,希望对您有所帮助。

本文链接:http://task.lmcjl.com/news/13315.html

展开阅读全文