Hibernate 中的乐观锁悲观锁是两种控制并发访问数据库的策略,确保多个用户/线程在访问相同数据时不会引起冲突。

1. 乐观锁 (Optimistic Locking)

乐观锁的基本思想是:在读取数据时并不加锁,而是假设不会发生并发冲突。只有在数据更新时,才会检查是否发生冲突。如果冲突发生,则拒绝当前更新操作。

实现方式:

  • 版本号机制:在数据库表中增加一个 version 字段,通常是一个整数或时间戳,每次数据更新时,版本号都会加1。每次更新时,Hibernate 会检查当前版本号和数据库中存储的版本号是否一致。如果一致,允许更新;如果不一致,说明在这期间数据被修改过,当前操作会抛出异常。 代码示例: @Entity public class Product { @Id private Long id; private String name; @Version private int version; // 版本号字段 }
  • 实现过程
    1. 当用户查询某个记录时,会同时读取版本号。
    2. 用户修改数据时,会带上当前的版本号。
    3. 当更新提交时,Hibernate 会通过版本号进行校验,确保当前版本号没有变更过。如果版本号不匹配,更新失败,抛出 OptimisticLockException

2. 悲观锁 (Pessimistic Locking)

悲观锁的基本思想是:认为会发生并发冲突,因此在读取数据时立即加锁,直到事务完成后才释放锁。这种方式可以避免并发冲突,但可能会影响系统性能,因为它会导致更多的锁竞争。

实现方式:

  • 数据库锁:Hibernate 支持通过数据库本身的锁机制来实现悲观锁。例如,使用 SQL FOR UPDATE 锁定选中的数据行。 代码示例: Query query = session.createQuery("FROM Product WHERE id = :id"); query.setParameter("id", productId); query.setLockMode("PESSIMISTIC_WRITE"); // 设置为悲观写锁 Product product = (Product) query.uniqueResult();
  • 实现过程
    1. 在查询时指定使用悲观锁,比如 PESSIMISTIC_READPESSIMISTIC_WRITE
    2. 读取的数据会被锁定,直到当前事务结束或释放锁。
    3. 如果另一个事务试图读取同一数据,它必须等待当前事务完成才能获得锁。

总结:

  • 乐观锁适用于冲突较少的场景,它通过版本控制避免并发冲突,通常在读取较频繁、写入较少的情况下使用。
  • 悲观锁适用于冲突较多的场景,它通过加锁确保数据一致性,但可能导致性能瓶颈,通常在需要频繁更新数据时使用。

两者的选择依赖于系统的并发性和性能需求,具体实现时可以根据实际情况决定使用哪种方式。