我可以为由Hibernate管理的表添加“ON DELETE CASCADE”吗?

22

我有一些由Hibernate管理的表格,其中包含各种外键约束。删除级联目前仅由Hibernate管理。为了测试数据,我经常手动创建和删除一些行。如果我可以将ON DELETE CASCADE添加到外键约束中,这将对我很有帮助,但我不知道Hibernate是否会因为数据库在它之前删除了某些内容而出现问题。


许多人似乎集中在DDL上,我的意图不是指示Hibernate使用SQL DELETE CASCADES创建DDL。我只想知道,如果我在数据库中指定了ON DELETE CASCADE,并在引用注释中使用JPA的cascade = CascadeType.REMOVE,例如@ManyToOne,是否会有任何问题。


我认为上面的答案只涵盖了Hibernate管理的级联,但我认为这就是你要找的:https://dev59.com/j3LYa4cB1Zd3GeqPSQB6 - Alan Hay
答案关于DDL创建,这对我来说不重要。我想知道在DB级别上CASCADE是否适用于Hibernate。事实上,存在@OnDelete注释_可能_证明它适用。 - musiKk
那只是一个起点。我不确定你是否同意这只涉及DDL。进一步的研究可能会引导您阅读Gavin King本人在此处发表的评论:http://www.mail-archive.com/hibernate-devel@lists.sourceforge.net/msg03803.html - Alan Hay
1
可能相关:https://dev59.com/VmEh5IYBdhLWcg3wjEDn - lanoxx
8个回答

6
你可以使用 CascadeType.DELETE,但是这个注解只适用于 EntityManager 中的对象,而不是数据库。你需要确保数据库约束中添加了 ON DELETE CASCADE。为了验证,你可以配置 JPA 生成一个 ddl 文件。查看 ddl 文件,你会注意到 ON DELETE CASCADE 不是约束的一部分。在 ddl 文件中将 ON DELETE CASCADE 添加到实际的 SQL 中,然后从 ddl 更新你的数据库模式。这将解决你的问题。
这个 link 展示了如何在 MySQL 中为 CONSTRAINT 使用 ON DELETE CASCADE。你可以在约束上执行此操作。你也可以在 CREATE TABLEALTER TABLE 语句中执行此操作。很可能 JPA 在一个 ALTER TABLE 语句中创建约束。只需将 ON DELETE CASCADE 添加到该语句即可。
请注意,一些 JPA 实现者确实提供了此功能的手段。
Hibernate提供了@OnDelete注释来实现此功能,因此最好使用它或者如果你想坚持使用标准的JPA功能,只需简单地更新ddl文件即可。

5
只要没有启用CascadeType.REMOVEorphanRemoval以防止实体移除事件的传播,您可以安全地使用ON DELETE CASCADE
在关系上有@OnDelete(action = OnDeleteAction.CASCADE)将强制Hibernate为相关的外键生成ON DELETE CASCADE。但是,您的Dialect必须指示支持此类关系,通过在supportsCascadeDelete方法中返回true来实现。 Dialect.supportsCascadeDelete() OnDelete

1
谢谢你的回答。我本来以为Hibernate会处理方言支持并且无论如何都会做正确的事情。显然,人们需要意识到这一点。这是我很高兴离开ORM的另一个原因...我希望我永远不必再次处理它们。 - musiKk

2
我看到两个潜在的问题:
  1. 如果代表你在数据库中直接级联操作的表的实体被版本化,那么它将无法工作,因为当Hibernate试图自己删除记录时,版本检查会失败(Hibernate会假设并发线程已经更新或删除了相应的记录)。
  2. 如果有用例,在这些用例中,你的业务逻辑会重新持久化这些实体实例,即使从父级级联删除了它们(例如,你正在删除旧的父级并将关联的子项迁移到新的父级,尽管为了更好的清晰度,如果存在这样的关联用例,我不会级联删除,但这取决于你,因为JPA规范允许),那么Hibernate将只取消计划删除子项,并仅删除父项,因此如果你在数据库中级联删除,仍然会得到已删除的子项。
也许在某些情况下还有其他可能出现问题的情况,所以我建议不要这样做。

0
从我的实验来看,我已经为此苦苦挣扎了几个小时,使用updatable = false在@JoinColumn中将防止Hibernate执行。
update theTable set theReference = null where theIdentifier = ?

例如(在 Kotlin 中):
@Entity
@Table(name = "course")
class Course(
    @Column(name = "title")
    var title: String? = null,
    @OneToMany(cascade = [CascadeType.PERSIST])
    @JoinColumn(name = "course_id", updatable = false)
    val reviews: MutableList<Review> = mutableListOf()
) {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    var id: Int? = null

}

0

您可以使用本地数据库功能,在删除父记录时删除子记录。

请注意双向关系,并确保仅指定级联插入和更新(以更安全的方式进行)。


0

你提到了测试目的。我猜想,执行一些测试、删除数据、重放测试...

当使用二级缓存或查询缓存时,如果直接从数据库中删除数据,则缓存将变得陈旧。 这可能会导致意外的测试结果。

所以,如果你使用二级/查询缓存,这将与Hibernate产生冲突,因为实体不会从缓存中清除。确保在直接删除任何数据后清除所有缓存。请参阅此问题以了解如何清除缓存。

官方的Hibernate 文档也提到了这一点:

请注意,缓存不知道其他应用程序对持久存储所做的更改。但是,可以配置它们定期过期缓存数据。


0
前几天我在这里寻找关于删除父行时删除子行的解决方案(级联删除),我似乎已经在我目前正在进行的项目中解决了这个问题。我使用Casade.ALL来处理我的双向多对一关系。 解决方案:
// Parent Entity.
@Entity
public class Parent implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "parent")
    private List<RequisitionDetail> children = new LinkedList<>();
    ...
    // Getters & Setters.
    ...
}

// Child Entity.
@Entity
public class Child implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "parent_id")
    private Parent parent;
    ...
    // Getters & Setters.
    ...
}

// Save your entities as below.
public void requisitionInsert() {
    try {
        Parent parent = new Parent();
        Child child = new Child();
        List<Child> children = new LinkedList<>();
        ...
        // Add parent to child
        child.setParent(parent);
        // Add child to list
        children.add(child);
        // Add children list to parent 
        parent.setChilds(children);
        // Save parent entity
        ParentDao.saveParent(parent);
    } catch (Exception e) {
        throw new RuntimeException();
    }
}

我的级联删除一开始失败了,因为我的父行没有子行,以这种方式解决了我的错误,级联删除也正常工作。


-2

不要使用cascade = CascadeType.REMOVE 这里的文档

因为你的数据库可能会被破坏。你可以使用正式的顺序。先删除子表,然后再删除主表。


问题不在于删除表格,而在于存储在表格中的数据。 - musiKk

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