问题标题基本上都说明了一切。是否可以在JPA / Hibernate中优雅地阻止从数据库中删除实体?我想要的是将实体标记为“隐藏”而不是实际删除它。
我还希望保留Cascade
语义,这样如果我尝试删除拥有某个其他实体集合的实体,则拥有实体和其集合中的每个实体都会被标记为隐藏而没有任何实体除了实现阻止删除的@PreRemove
处理程序并将实体标记为隐藏之外,我还需要额外的工作。
这是可能的,还是我需要弄清楚其他方法?
答案 0 :(得分:12)
JPA / Hibernate是否可以优雅地阻止从数据库中删除实体?
是的,只要您避免使用EntityManager.remove(entity)
,这是可能的。如果您使用EntityManager.remove()
,那么JPA提供程序将使用相应的SQL DELETE语句标记要删除的对象,这意味着一旦您标记要删除的对象,就无法使用优雅的解决方案。
在Hibernate中,您可以使用@SQLDelete
and @Where
annotations实现此目的。但是,这不适用于JPA,因为已知EntityManager.find()
会忽略@Where
注释中指定的过滤器。
@PreUpdate
和@PrePersist
注释挂钩实体生命周期事件,以确保在持久化和更新事件上更新标志。同样,您需要确保不会调用EntityManager.remove
方法。
我建议使用@PreRemove
注释挂钩为删除实体而触发的生命周期事件,但使用实体侦听器来防止删除由于下述原因而充满麻烦:
SQL DELETE
在逻辑意义上发生,您需要在同一事务中保留对象以重新创建它 * 。唯一的问题是,在EntityListener中引用EntityManager
并通过推理中的推理调用EntityManager.persist
并不是一个好的设计决策。基本原理非常简单 - 您可能最终在EntityListener中获取不同的EntityManager引用,这只会导致应用程序中出现模糊和混乱的行为。SQL DELETE
发生,那么您必须在EntityListener中抛出异常。这通常最终会回滚事务(特别是如果Exception是RuntimeException或声明为导致回滚的应用程序异常),并且不提供任何好处,因为整个事务将被回滚。如果您可以选择使用EclipseLink而不是Hibernate,那么如果您定义适当的DescriptorCustomizer
或使用AdditionalCriteria
注释,则可能会出现一个优雅的解决方案。这两个似乎都适用于EntityManager.remove
和EntityManager.find
调用。但是,您可能仍需要编写JPQL或本机查询来考虑逻辑删除的实体。
* JPA Wikibook on the topic of cascading Persist:
中概述了这一点如果删除一个对象以删除它,如果然后在对象上调用persist,它将恢复该对象,并且它将再次变为持久性。如果是有意的话,这可能是期望的,但JPA规范也要求级联持久化的这种行为。因此,如果删除一个对象,但忘记从级联持久关系中删除对它的引用,则将忽略该删除。