Hibernate为何在实体无实际变更时仍标记集合为脏数据并触发更新?
Hibernate为何在实体无实际变更时仍标记集合为脏数据并触发更新?
首先得搞清楚Hibernate对集合型属性的脏检查逻辑——它的判断逻辑其实比你想象的要“直白”:
- 首先看集合的引用是否发生变化:如果你把原实体里的集合替换成了一个新的集合对象(哪怕内容完全一样),Hibernate会直接判定集合为脏数据,不会再去深度对比元素内容。
- 如果集合引用没变,它才会去对比集合的结构变化:比如元素的数量、顺序,以及元素本身的equals结果(这也是你之前验证equals方法的原因)。
回到你的场景,问题大概率出在Jackson反序列化+自定义Deserializer的环节上:
你用objectMapper.readerForUpdating(originalEntity)来更新原实体,但你的ConnectionDeserializer在处理connections的时候,是不是重新创建了一个List对象,然后把处理后的元素放进去,再赋值给updatedEntity.connections?如果是这样,哪怕新集合的内容和原集合完全一致,Hibernate检测到集合引用变了,就会标记为脏数据,进而触发版本号递增和关联表更新。
你之前尝试的用Set替代List、把Connection改成实体这些操作,本质上都没解决“集合引用被替换”的问题,所以脏检查还是会触发。
那该怎么解决呢?给你几个可行的方案:
方案1:修改Deserializer逻辑,复用原集合引用
核心思路是不要创建新的集合,而是在原实体的connections集合上直接做增删改操作。
比如在你的ConnectionDeserializer里,先获取原实体中已有的connections集合,然后根据输入的patch数据做diff:
- 遍历输入的connections,更新原集合中匹配的元素
- 添加原集合中没有的新元素
- 删除标记了delete的元素
这样集合的引用从头到尾都没变,Hibernate就会去深度对比元素内容,当内容完全一致时,就不会标记为脏数据了。
方案2:手动清除集合的脏标记(兜底方案)
如果修改Deserializer的成本比较高,可以在保存实体前,手动检查集合是否真的有变化,如果没有就清除脏标记。需要用到Hibernate的底层API:
// 先对比原集合和处理后的集合内容,如果完全一致 Session session = entityManager.unwrap(Session.class); PersistentCollection connectionsCollection = (PersistentCollection) session.getPersistenceContext() .getCollectionEntry(originalEntity.getConnections()) .getCollection(); connectionsCollection.clearDirty(); // 清除脏标记
不过这个方法比较依赖Hibernate的内部实现,尽量作为兜底方案,优先用方案1从根源解决问题。
额外注意点
- 确保你的
Connection类的equals和hashCode方法是正确实现的:虽然你说已经验证过,但这是Hibernate对比集合元素的基础,一定要保证两个内容相同的Connection实例调用equals返回true。 - 避免在代码中随意重新赋值集合属性:比如不要写
entity.setConnections(new ArrayList<>())这种代码,尽量在原集合上做修改。
备注:内容来源于stack exchange,提问作者A. Inbar
相关产品推荐
相关产品推荐

