当实体未更改时,Hibernate 在刷新期间保持实体

Posted

技术标签:

【中文标题】当实体未更改时,Hibernate 在刷新期间保持实体【英文标题】:Hibernate is persisting entity during flush when the entity has not changed 【发布时间】:2010-05-18 23:06:22 【问题描述】:

我遇到了一个问题,即实体管理器正在持久化一个我认为在刷新期间没有改变的实体。

我知道以下代码是问题所在,因为如果我将其注释掉,实体就不会存在。在这段代码中,我所做的只是加载实体并调用一些 getter。

Query qry = em.createNamedQuery("Clients.findByClientID");
qry.setParameter("clientID", clientID);

Clients client = (Clients) qry.getSingleResult();

results.setFname(client.getFirstName());
results.setLname(client.getLastName());
...
return results;

稍后在另一种方法中,我执行另一个 namedQuery,这会导致实体管理器刷新。由于某种原因,上面加载的客户端被持久化了。

这是一个问题的原因是因为在这一切的中间,有一些旧代码正在对客户端进行一些直接的 JDBC 更改。当实体管理器持久化时,直接 JDBC 所做的更改将丢失。

我们目前的理论是,实体管理器将实体与基础记录进行比较,发现它不同,然后将其持久化。

有人可以解释或确认我们看到的行为吗?

【问题讨论】:

【参考方案1】:

我不知道hibernate的内幕,但我猜你是对的。但是我不认为hibernate和db相比,而是和本地会话缓存相比。

您可以通过将对象设置为只读来避免该问题 - 休眠然后不检查更改,例如

   session.setReadOnly(client, true);

或者,您也可以使用 Session.evict() 驱逐对象。下次需要该对象时,将从数据库中读取该对象,包括您使用自定义 JDBC 所做的任何更改。

【讨论】:

如果它与本地会话缓存进行比较,那么 jdbc 调用如何与之交互。 jdbc 应该直接进入数据库并跳过会话(或者我认为)。我正在注入数据源,这会有所作为吗? 如果您的 jdbc 层使用相同的 DataSource 并且 DataSource 是 SingleConnectionDataSource,那么如果 hibernate 调用在 jdbc 调用之前刷新,它们会进行交互【参考方案2】:

您是对的,实体管理器比较对象(只要它们在会话中不是只读的、被驱逐、已调用 session.clear() 等),如果它们不匹配,则它会冲洗掉它们。

这个特定的问题曾经困扰过我们,并且导致了完全相同的不必要的刷新问题——我们的也是一个性能问题,在一个紧密的循环中,我们需要flush() 会话并且每次我们执行数百个未更改的对象被转储回数据库。

在我们的例子中,有问题的实体没有正确地实现了equals()hashCode()——Hibernate 显然依赖于它们来检查对象是否已更改,以及它们是否已更改在某些方面不正确,那么它别无选择,只能将它们刷新。

根据记忆(不久前),它实际上是一个没有正确实现hashCode() 的附属实体——想象一下Student-Teacher 关系,其中Teacher 没有正确实现这些方法。当对Student 进行脏检查时,Hibernate 在其实体缓存中检查Teacher,认为它不存在(由于不正确的实现),因此认为Student 已更改,重新刷新它以写出新的@987654331 @ID。

因此请检查hashCode()equals() 以获取相关实体、所有相关实体以及写入数据库的任何其他组件/用户类型。

【讨论】:

您如何确定这是哈希码或等号的不当实现? 不幸的是,进行了很多非常痛苦的调试。我不记得我们能够打开的 Hibernate 中是否有有用的日志记录;我认为最后,我们只是通过 Hibernate 代码中的几个战略断点解决了这个问题。从 DefaultFlushEntityEventListener.dirtyCheck() 开始——它会找到所有脏属性,因此应该清楚哪些属性导致了刷新。实际上,我们最终编写了一个 JUnit 测试用例,它爬出了类路径,找到了所有实体,并检查了它们在 hashCode() 和 equals() 方面的行为是否正确,这也发现了其他一些实体。 我们有一个自定义用户类型将数据库中的日期从 0000-00-00 转换为空值。我们怀疑这可能是设置脏标志。按照您的建议使用dirtyCheck,我们应该能够验证这是否是来源。如果是……有没有办法说不要让这个更改设置脏标志? 我认为你不能排除它。我怀疑正在发生的事情是您的 bean 的 getter 没有返回 Hibernate 期望的相同值 - 也就是说,Hibernate 从您的 bean 中看到“null”,但从脱水的数据库状态中看到“0000-00-00”。不是 100% 确定该比较是如何完成的,但 DefaultFlushEntityEventListener 中的代码应该可以帮助您解决问题。 原来我们的 Equals 实现是错误的。一旦我们得到正确的逻辑,实体就会意外退出写入数据库。【参考方案3】:

如果您的 getter 正在执行任何逻辑,因此它返回的内容与 hibernate 发送给 setter 的内容不同,那么它将被标记为脏。例如,如果你的 getter 逻辑在 hibernate 给你一个数据库中的 null 时返回 0,那么它将被标记为脏,因为它看起来像是改变了。我不知道有一种方法可以告诉 hibernate 不要将该更改标记为使实体变脏。

【讨论】:

为此干杯,我收到此错误消息“应用程序试图编辑只读项目://无法写入只读对象”,您的帖子为我指明了正确的方向。也就是说,我在 setter 上制作了一个防御性副本,并在 getter 上返回了一个新的 TreeSet(原因尚不清楚)。删除该逻辑后一切正常! 这是一次经验教训难以忘记的事情:)

以上是关于当实体未更改时,Hibernate 在刷新期间保持实体的主要内容,如果未能解决你的问题,请参考以下文章

怎样解决hibernate中一级缓存导致数据不能刷新

移动 Hibernate 工具以构建时间

保存后刷新并获取实体(JPA/Spring Data/Hibernate)

刷新网站时样式表未更新

Nativescript Vue:当数据更改时,RadListView 不刷新

未调用加载程序 onLoadFinished()