防止Hibernate会话刷新/存储无效的脏实体

10
我想知道如何处理才能防止Hibernate 4.3.4(带有Spring和Hibernate Validator)刷新已更改的实体。在我的代码中,我使用了Hibernate Validator的手动实现(实例内部的.validate()方法),该方法在保存实体之前被调用。如果发现错误,则validate()方法返回错误列表,否则将调用Session.update()来存储实体,随后是事务提交。

这个方法可以工作,但当实例本身被操作时(例如,请求参数在实体中设置),实体和相应的Hibernate会话被标记为“已更改”,并且下一个Session.flush()会将实体存储起来。
在我的情况下,我希望对可能被存储的实体进行明确的控制,并防止存储任何脏实体,请问我该如何做到这一点?
编辑: 我知道我可以通过逐出实体(或通过合并清除并重新引入实体)手动管理此过程,但这不是我的目的。我希望具有偏移量的情况,即未明确保存并且事务未明确提交的任何实体都不会存储到数据库中(例如,通过拦截器)。
1个回答

12
如果您修改实体并明确希望它们不被刷新,您可以在修改之前将其分离。分离的实体将不再由持久化上下文管理。
Hibernate API:

session.evict(myEntity)

JPA:

entityManager.detach(myEntity)

编辑: 如果您想分离所有实体并仅管理其中的一些,您可以先清除持久性上下文,然后合并您需要管理的实体:

Hibernate API:

session.clear();
managedEntity = session.merge(detachedEntity);

JPA:
entityManager.clear();
managedEntity = entityManager.merge(detachedEntity);

编辑2 所有对受管理实体的更改都会在事务提交时刷新。我不知道JPA或Hibernate有任何可以关闭此行为的功能。因此,除了分离一些或所有实体外,您还有其他选择,但没有一个完全符合您的要求:

  • 在事务之外获取实体,这样它们就会立即分离。这似乎最符合您的要求——不需要管理更改,只有显式合并才能保存实体,您将少与持久性API打交道。然而,您仍然需要打开一个会话来合并那些您希望保存更改的实体。

  • 使用查询中的NEW运算符将查询结果映射到DTO/POJO(始终处于分离状态)。这种方法具有将持久性映射与应用程序分离的优点。但是,引入一堆新类可能不值得,而在整个应用程序中不一致地执行此操作会增加概念复杂性。

  • 在事务内工作,但回滚而不是提交。您将无法保存任何内容,这是一种粗略的防止更改与数据库同步的方法。不幸的是,您最终必须提交或回滚事务。

  • 创建实体的深层副本以更改它们。坦白地说,这对我来说没有多大意义,只是为了完整性而添加。

编辑3 尽管JPA规范没有明确规定,但Hibernate和Eclipselink都允许将事务或甚至单个查询的结果集标记为只读。这对你可能很有用,因为当持久对象为只读时,Hibernate不会检查简单属性的脏数据情况。在涉及关系时,更改刷新变得有些复杂。请参阅Hibernate的此文档Eclipselink的此文档

嗨Kostja,那是“反向”解决方案,但不是我想要的。我想要的是相反的情况,只有明确保存和提交的实体可以被刷新 - 默认情况下,如果可能的话,不要将它们分离。 - Marius
当然,您可以双向修改持久化上下文。请参见编辑。 - kostja
差不多了,但还不完全是我想要的。我真的不想担心脏实体并以你所描述的方式调节持久性,而是通过拦截器来实现,例如,当这些实体没有被显式保存且事务没有被显式提交时,永远不会(自动)刷新它们,默认情况下,不会重新引入它们到Hibernate会话中,也不需要在刷新之前将它们清除。 - Marius
据我所知,您无法配置持久性提供程序以按您希望的方式运行。但是还有其他一些选项需要考虑。请参见编辑。 - kostja
感谢kostja的优秀反馈,我将调查另一种方法,使用一个拦截器和实现findDirty和onDirtyFlush的方法,这可能会让我摆脱脏提交。但正如你已经表达的那样,这与持久上下文的本质相违背。 - Marius
不用谢。我之前不知道还有另一种选项 - 只读实体。也许它更适合你的需求。个人而言,我会避免改变Hibernate的正常工作方式。对于刚接触你代码的同事来说,这可能会带来不愉快的惊喜。请再次查看编辑 :) - kostja

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接