指定READ_COMMITTED隔离级别,JPA却表现出REPEATABLE_READ行为
问题分析与解决:READ_COMMITTED隔离级别下JPA读取旧数据的问题
核心原因:Hibernate一级缓存(Session缓存)的拦截
你配置的READ_COMMITTED隔离级别本身没问题,但Hibernate的**一级缓存(Session级缓存)**会优先返回已加载的实体实例,完全跳过后续的数据库查询——哪怕你用了FOR UPDATE锁。
你的代码流程里:
- 第一次执行
findFirstCountGreaterThanOrderByRandom(4)时,目标Counts实体已经被加载到当前Session的一级缓存中; - 后续调用
findById(counts.getId())时,Hibernate会先检查一级缓存,发现该ID的实体已存在,直接返回缓存里的旧实例,根本不会执行你预期的带FOR UPDATE的SQL去数据库读取最新值。这就造成了看起来像REPEATABLE_READ的行为,但本质是缓存逻辑的影响,和数据库隔离级别无关。
验证与解决方案
方案1:强制刷新实体,从数据库重新拉取
调用EntityManager.refresh()方法,强制让Hibernate从数据库读取最新数据覆盖缓存:
counts = myRepo.findById(counts.getId()); entityManager.refresh(counts); // 强制刷新,获取最新值 counts.setCount(1); log.info("counts = {}", counts);
方案2:绕过一级缓存执行查询
修改Repository方法,通过查询提示禁用缓存,让查询直接走数据库:
@Repository public interface CountsRepo extends JpaRepository<Counts, Long> { @QueryHints(value = { @QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "false") }) Counts findById(Long id); }
或者在查询时设置刷新模式为REFRESH:
counts = myRepo.findById(counts.getId()).withFlushMode(FlushModeType.COMMIT).get();
方案3:拆分查询到不同Session/事务
把第一次随机查询和后续的锁查询放到独立的事务中,这样第一次查询的实体不会进入后续事务的Session缓存:
// 无事务或独立事务的查询方法 public Counts getRandomCount() { return myRepo.findFirstCountGreaterThanOrderByRandom(4); } // 带隔离级别的事务方法 @Transactional(isolation = Isolation.READ_COMMITTED) public void testCount() { Counts counts = getRandomCount(); counts = myRepo.findById(counts.getId()); counts.setCount(1); log.info("counts = {}", counts); }
补充说明
你测试步骤2能得到正确结果,是因为第一次查询在MySQL终端执行,和应用的Session完全无关,应用的Session中没有缓存该实体,所以findById会直接执行带FOR UPDATE的SQL,拿到数据库最新值。
内容的提问来源于stack exchange,提问作者Alexis
相关产品推荐
相关产品推荐

