JPA/休眠 - 在预删除处理程序中阻止删除?

2022-09-03 07:32:54

问题标题基本上说明了一切。在 JPA/Hibernate 中,是否可以优雅地阻止从数据库中删除实体?我想将实体标记为“隐藏”,而不是实际删除它。

我还希望保留语义,这样,如果我尝试删除拥有其他实体集合的实体,则拥有实体及其集合中的每个实体都会被标记为隐藏,而无需我进行任何额外的工作,除了实现防止删除并将实体标记为隐藏的处理程序之外。Cascade@PreRemove

这是可能的,还是我需要找出其他方法?


答案 1

在 JPA/Hibernate 中,是否可以优雅地阻止从数据库中删除实体?

是的,只要你避免使用这个是可能的。如果您确实使用 ,则 JPA 提供程序将使用相应的 SQL DELETE 语句来标记要删除的对象,这意味着一旦您标记要删除的对象,就不可能有一个优雅的解决方案。EntityManager.remove(entity)EntityManager.remove()

在 Hibernate 中,您可以使用@SQLDelete@Where注释来实现此目的。但是,这不适用于 JPA,因为已知会忽略注释中指定的过滤器。EntityManager.find()@Where

因此,仅 JPA 解决方案涉及在实体类中添加一个标志(即一列),以将数据库中逻辑上已删除的实体与“活动”实体区分开来。您需要使用适当的查询(JPQL 和本机查询)来确保逻辑上删除的实体在结果集中不可用。可以使用 和 注释挂接到实体生命周期事件,以确保在持久和更新事件上更新标志。同样,您需要确保不会调用该方法。@PreUpdate@PrePersistEntityManager.remove

我本来建议使用注释来挂接到为删除实体而触发的生命周期事件,但是由于以下原因,使用实体侦听器来防止删除充满了麻烦:@PreRemove

  • 如果需要在逻辑意义上防止 发生,则需要在同一事务中保留对象以重新创建它*。唯一的问题是,在 EntityListener 中引用 和 通过推理在侦听器中调用 它不是一个好的设计决策。基本原理非常简单 - 您可能最终在 EntityListener 中获取不同的 EntityManager 引用,这只会导致应用程序中出现模糊和令人困惑的行为。SQL DELETEEntityManagerEntityManager.persist
  • 如果需要防止事务本身发生,则必须在 EntityListener 中引发异常。这通常最终会回滚事务(特别是如果异常是运行时异常或应用程序异常,它被声明为导致回滚的异常),并且不提供任何好处,因为整个事务将被回滚。SQL DELETE

如果可以选择使用 EclipseLink 而不是 Hibernate,那么如果您定义适当的描述符自定义程序或使用 AdditionalCriteria 注释,似乎可以找到一个优雅的解决方案。这两种操作似乎都与 和 调用配合得很好。但是,您可能仍然需要编写 JPQL 或本机查询来考虑逻辑上删除的实体。EntityManager.removeEntityManager.find


*这在JPA维基教科书中概述了级联持久化的主题

如果删除对象以将其删除,则随后对该对象调用 persist,它将复活该对象,并且它将再次变为持久性。如果这是有意的,则可能需要这样做,但 JPA 规范也要求此行为才能使级联持续存在。因此,如果删除某个对象,但忘记从级联持久关系中删除对该对象的引用,则将忽略该删除。


答案 2

推荐