无需调用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
相关产品推荐
相关产品推荐

