当前位置:首页 > 行业动态 > 正文

Hibernate悲观锁和乐观锁实例详解

Hibernate悲观锁通过@Lock或查询.setLockMode加锁,如session.lock(entity, LockMode.PESSIMISTIC_WRITE);乐观锁依赖@Version字段实现,更新时校验版本增量,如@Entity+@Version private Integer version

Hibernate悲观锁乐观锁实例详解

锁机制核心概念

在数据库并发场景中,锁机制用于保证数据一致性,Hibernate作为ORM框架,通过抽象底层数据库锁机制,提供两种主流并发控制策略:悲观锁(Pessimistic Locking)乐观锁(Optimistic Locking),两者本质区别在于对并发冲突的预判和处理方式。

特性 悲观锁 乐观锁
冲突预判 假设冲突必然发生 假设冲突概率极低
加锁时机 读取时立即加锁 提交时检测冲突
数据库依赖 依赖数据库排他锁(如MySQL行锁) 依赖版本号/时间戳机制
性能开销 高(持有锁期间阻塞其他操作) 低(无锁,仅冲突时回滚)
典型场景 高冲突写操作(如库存扣减) 低冲突读/写(如用户配置修改)

悲观锁实现与实例

实现原理:通过数据库原生锁机制(如SELECT ... FOR UPDATE)锁定数据行,阻止其他事务修改。
Hibernate实现:使用LockOptionsLockMode接口显式加锁。

// 实体类示例
@Entity
public class Account {
    @Id
    private Long id;
    private Double balance;
    // getters/setters
}
// 悲观锁使用示例
public void deductBalance(Long accountId, Double amount) {
    Session session = sessionFactory.openSession();
    session.beginTransaction();
    // 获取悲观写锁(等效于SELECT ... FOR UPDATE)
    Account account = session.get(Account.class, accountId,
        LockOptions.upgradable(LockMode.PESSIMISTIC_WRITE));
    // 业务逻辑
    account.setBalance(account.getBalance() amount);
    session.update(account);
    session.getTransaction().commit();
    session.close();
}

底层SQL分析

-Hibernate生成的SQL
SELECT  FROM Account WHERE id = ? FOR UPDATE

此语句会锁定id=1的行,其他事务无法修改直至本事务提交。

Hibernate悲观锁和乐观锁实例详解  第1张

乐观锁实现与实例

实现原理:通过版本号(@Version)或时间戳标记数据状态,提交时检查版本是否一致。
Hibernate实现:使用@Version注解维护版本号,框架自动处理版本校验。

// 实体类示例
@Entity
public class Product {
    @Id
    private Long id;
    private String name;
    private Integer stock;
    @Version // 版本号字段(必须为整数类型)
    private Integer version;
    // getters/setters
}
// 乐观锁使用示例
public void updateStock(Long productId, Integer quantity) {
    Session session = sessionFactory.openSession();
    session.beginTransaction();
    Product product = session.get(Product.class, productId);
    if (product.getStock() >= quantity) {
        product.setStock(product.getStock() quantity);
        session.update(product); // 内部自动检查version
    } else {
        throw new RuntimeException("库存不足");
    }
    session.getTransaction().commit();
    session.close();
}

并发冲突处理
当两个事务同时修改同一数据时,后提交的事务会触发OptimisticLockException(Hibernate封装的StaleStateException),需手动捕获处理:

try {
    session.update(product);
    transaction.commit();
} catch (OptimisticLockException e) {
    // 重试逻辑或提示用户冲突
}

并发场景对比测试

模拟环境:两个并发事务同时操作id=1的账户。

场景 悲观锁行为 乐观锁行为
事务A操作 成功获取锁,执行扣减 读取版本号(如version=1)
事务B操作 被阻塞,直到事务A提交 读取版本号(仍为version=1)
事务A提交 释放锁,数据提交 版本号递增为version=2
事务B提交 (若未修改)正常提交 版本冲突,抛出异常

选型建议与最佳实践

  1. 悲观锁适用场景

    • 高频写操作(如电商库存、订单处理)
    • 长事务中需保证数据实时性
    • 数据库支持行级锁(如MySQL InnoDB)
  2. 乐观锁适用场景

    • 读多写少的业务(如用户资料修改)
    • 并发量较低或冲突概率小的场景
    • 需要避免数据库死锁问题
  3. 混合使用策略

    • 对核心数据(如资金相关)使用悲观锁
    • 对辅助配置数据使用乐观锁
    • 结合@Lock注解动态控制锁类型

常见问题解答(FAQs)

Q1:乐观锁出现OptimisticLockException后如何处理?
A1:应捕获异常并采取以下措施:

  • 提示用户数据已被修改,重新加载最新数据
  • 实现重试机制(最多重试3次)
  • 记录日志用于排查并发冲突原因

Q2:悲观锁会阻塞其他读操作吗?
A2:这取决于数据库隔离级别:

  • REPEATABLE READ(如MySQL默认):写锁会阻塞其他事务的读操作
  • READ COMMITTED:读操作不会阻塞,但只能读取已提交数据
    建议在高并发场景中谨慎使用悲观锁,或通过`Lock
0