You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无需调用EntityManager.refresh,如何通过PESSIMISTIC_WRITE锁获取最新数据?

解决Hibernate PESSIMISTIC_WRITE锁查询返回旧数据的问题

问题根源

你遇到的核心问题是Hibernate一级缓存(Session级缓存)的存在:当同一个EntityManager(Session)中之前加载过该Account实体,后续即使通过加PESSIMISTIC_WRITE锁的JPQL查询,Hibernate也会优先返回一级缓存中的旧对象,而不会去数据库拉取最新数据。你之前尝试的JPA缓存提示(javax.persistence.cache.*)只作用于二级缓存,对一级缓存无效。

解决方案

1. 使用Hibernate专属Hint强制绕过一级缓存

添加Hibernate的org.hibernate.cacheMode提示,设置为CacheMode.REFRESH,强制Hibernate从数据库获取最新数据并更新缓存:

public Account findByCodeWithLock(String code) throws Exception {
    TypedQuery<Account> query = em.createQuery("select a from Account a where a.accountCode = :code", Account.class);
    query.setParameter("code", code);
    query.setLockMode(LockModeType.PESSIMISTIC_WRITE);
    // 关键:强制从数据库刷新,忽略一级缓存
    query.setHint("org.hibernate.cacheMode", CacheMode.REFRESH);
    logger.info("lock");
    Account account = query.getSingleResult();
    logger.info("getSingleResult: {}", account.getBalanceAmount());
    // 无需再调用em.refresh()
    return account;
}

2. 手动清理一级缓存中的旧实体

如果知道Account的主键,可以先检查并分离缓存中的旧实例,再执行查询:

public Account findByCodeWithLock(String code) throws Exception {
    // 先通过accountCode查询主键(假设已有对应查询方法)
    Long accountId = getAccountIdByCode(code);
    if (accountId != null) {
        Account cachedAccount = em.find(Account.class, accountId);
        if (cachedAccount != null) {
            // 分离缓存中的旧实体,让后续查询去数据库拉取最新数据
            em.detach(cachedAccount);
        }
    }

    TypedQuery<Account> query = em.createQuery("select a from Account a where a.accountCode = :code", Account.class);
    query.setParameter("code", code);
    query.setLockMode(LockModeType.PESSIMISTIC_WRITE);
    return query.getSingleResult();
}

3. 使用原生SQL加锁查询

绕过Hibernate的JPQL缓存机制,直接执行Oracle的SELECT ... FOR UPDATE语句,确保获取最新数据:

public Account findByCodeWithLock(String code) throws Exception {
    Query nativeQuery = em.createNativeQuery(
        "SELECT * FROM ACCOUNT WHERE ACCOUNT_CODE = ? FOR UPDATE", 
        Account.class
    );
    nativeQuery.setParameter(1, code);
    return (Account) nativeQuery.getSingleResult();
}

4. 改用EntityManager.find()加锁(限主键查询)

如果你的查询可以通过主键进行,使用EntityManager.find()结合锁模式,Hibernate会自动验证缓存并从数据库刷新数据:

public Account findByIdWithLock(Long accountId) throws Exception {
    return em.find(Account.class, accountId, LockModeType.PESSIMISTIC_WRITE);
}

关键说明

  • PESSIMISTIC_WRITE锁的作用是锁定行防止并发修改,但不负责自动刷新Hibernate一级缓存中的旧数据,你需要显式控制缓存行为。
  • Oracle默认隔离级别是READ COMMITTED,加SELECT ... FOR UPDATE后会确保查询到的是最新已提交数据,只要绕过一级缓存就能拿到最新值。

内容的提问来源于stack exchange,提问作者Bruno Fonseca

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 12:17:33