当实体未更改时,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 在刷新期间保持实体的主要内容,如果未能解决你的问题,请参考以下文章
保存后刷新并获取实体(JPA/Spring Data/Hibernate)