下面我就为您讲解Spring Boot2 + JPA悲观锁和乐观锁实战教程的完整攻略。
悲观锁是指,当在执行某一操作时,认为别的并发操作会对其产生影响,因此在执行前进行加锁,使得其他并发操作无法操作,直到该操作完成释放锁。
在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
,表示使用悲观写锁。
下面我们以一个简单的订单支付接口(为了订单操作的幂等性,只进行部分支付)为例来实现悲观锁。
首先,在 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。但是,为了避免超卖现象,我们需要在减库存前查询库存数量并使用悲观写锁进行加锁,以确保操作的正确性。
首先,在 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
关键字加锁,以确保操作的正确性。
乐观锁是指,当在执行某一操作时,认为别的并发操作不会对其产生影响,因此在执行前不进行加锁,而是在更新操作时判断该记录的版本是否已被其他并发操作修改,如果该记录的版本未被修改,则更新成功;否则更新失败,并重新尝试更新。
在JPA中,实现乐观锁可以通过 @Version
注解来实现。当更新实体时,如果版本号发生变化,则更新失败并抛出 OptimisticLockException
异常。
我们仍然以订单为例,使用 @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。
再以商品为例,使用 @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