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

EntityManager#refresh方法未拉取数据库最新值反而重置缓存修改原因求解

JPA EntityManager refresh()方法返回旧值问题解析

问题相关代码如下:

em.getTransaction().begin();
        
Person person = em.find(Person.class, 2L); //Person[id=2L, age=23]
person.setAge(24); 
System.out.println(person.getAge()); //此处输出24
        
//id为2的Person实体对应数据库记录被并发修改,
//PERSON表中该条记录的age字段被SQL语句(例如"UPDATE person SET age=25 WHERE id=2")改为25

em.refresh(person); // 预期通过SELECT查询拉取数据库最新值刷新实体
System.out.println(person.getAge()); //此处输出23而非25,原因是什么?
        
em.getTransaction().commit();
em.close(); 

底层逻辑说明

  • refresh()方法的原生定义:JPA规范明确规定,refresh(Object entity)方法的核心作用是丢弃持久化上下文中该实体的所有未持久化修改,强制从数据库加载最新状态覆盖实体属性。你之前执行的person.setAge(24)属于未提交的临时修改,调用refresh后会被直接清空,这属于符合规范的正常行为。
  • 数据库事务隔离级别导致读到旧值:绝大多数关系型数据库(比如MySQL InnoDB引擎)默认的事务隔离级别为可重复读(REPEATABLE READ),该隔离级别实现了同一个事务内的所有快照读都使用事务内第一次查询生成的一致性视图,不会读取到其他事务提交的修改。你当前事务中第一次em.find时已经生成了age=23的一致性快照,后续refresh触发的SELECT查询仍然使用该快照,所以只会读到23,无法读到其他事务修改的25。

修复方案

如果需要refresh时读取到数据库的最新修改,可采用以下两种方式:

  • 调整当前事务的隔离级别为读已提交(READ COMMITTED),该隔离级别下每次查询都会获取最新的已提交数据快照,refresh触发的查询就能读到25的最新值。
  • 调用带锁的refresh方法强制走当前读,代码如下:
em.refresh(person, LockModeType.PESSIMISTIC_WRITE);

该方式会强制绕过一致性快照,直接读取数据库最新的已提交记录,就能拿到其他事务修改的25。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 22:06:05