Hibernate返回已修改对象而非新实例问题求助
我之前处理过类似的场景,@Transient字段因为Hibernate完全不追踪它的状态,确实会导致一级缓存里的实体带着修改后的脏数据“赖着不走”。给你几个不用深拷贝的解决方案,按需选择:
1. 手动从Session缓存中移除目标实体
Hibernate的一级缓存(Session级)会持有查询过的实体实例,只要Session没销毁,下次查询就会直接返回缓存里的对象。你可以手动把目标A实体从缓存中detach掉,这样后续查询就会从数据库重新加载全新实例:
@Autowired private EntityManager entityManager; public A getFreshA(EmbeddedId id) { // 先尝试获取缓存中的实体 Optional<A> cachedA = aRepository.findById(id); if (cachedA.isPresent()) { // 将实体从Session缓存中移除 entityManager.detach(cachedA.get()); } // 重新查询,此时会从数据库加载新实例 return aRepository.findById(id).orElse(null); }
如果需要清空整个Session的缓存(适合批量刷新场景),可以用entityManager.clear(),但注意这会清除所有缓存的实体,谨慎使用。
2. 给查询方法添加缓存提示,强制从数据库加载
在Spring Data JPA的Repository查询方法上,通过Hibernate的查询提示,指定每次查询都绕过缓存、直接从数据库获取最新数据:
@Repository public interface ARepository extends JpaRepository<A, EmbeddedId> { @QueryHints(value = { @QueryHint(name = org.hibernate.annotations.QueryHints.HINT_CACHE_MODE, value = "REFRESH") }) Optional<A> findById(EmbeddedId id); }
这里的CacheMode.REFRESH会让Hibernate从数据库加载数据,同时更新缓存;如果想完全忽略缓存,可以把值改成IGNORE。
3. 调用EntityManager.refresh()同步实体状态
如果已经拿到了缓存中的A实体,可以调用refresh()方法,强制Hibernate从数据库重新加载该实体及其关联对象,覆盖缓存中的脏数据:
public A refreshA(EmbeddedId id) { A cachedA = aRepository.findById(id).orElse(null); if (cachedA != null) { // 从数据库重新加载实体及关联的B集合 entityManager.refresh(cachedA); // 如果B集合是懒加载,需要触发加载才能拿到新实例 cachedA.getBs().size(); } return cachedA; }
注意:refresh()只会同步数据库中存在的字段,@Transient字段会被重置为默认值(因为数据库里没有这个字段),正好符合你要的“获取数据库新实例”的需求。
4. 使用DTO投影,避免直接操作实体类
如果业务场景允许,建议查询时返回DTO(数据传输对象)而非实体类。DTO是纯POJO,不与Hibernate缓存绑定,每次查询都会生成新的DTO实例,自然不会有脏数据问题:
首先定义对应DTO:
// ADTO.java public class ADTO { private EmbeddedId id; private List<BDTO> bs; // 构造函数(要和查询语句中的字段顺序对应) public ADTO(EmbeddedId id, List<BDTO> bs) { this.id = id; this.bs = bs; } // getter方法 } // BDTO.java public class BDTO { // 包含B实体的非Transient字段,以及需要的value字段 private String value; // 其他字段... public BDTO(String value, /* 其他字段 */) { this.value = value; // 初始化其他字段 } // getter方法 }
然后在Repository中定义投影查询:
@Repository public interface ARepository extends JpaRepository<A, EmbeddedId> { @Query("SELECT new com.example.ADTO(a.id, COLLECT(new com.example.BDTO(b.value, b.xxx))) FROM A a JOIN a.bs b WHERE a.id = :id GROUP BY a.id") Optional<ADTO> findADTOById(@Param("id") EmbeddedId id); }
这种方式彻底隔离了业务操作和Hibernate实体缓存,是最干净的长期解决方案。
内容的提问来源于stack exchange,提问作者Derek Noble

