JPA:单向多对一关系和级联删除

127

假设我有一个如下所示的单向@ManyToOne关系:

@Entity
public class Parent implements Serializable {

    @Id
    @GeneratedValue
    private long id;
}

@Entity
public class Child implements Serializable {

    @Id
    @GeneratedValue
    private long id;

    @ManyToOne
    @JoinColumn
    private Parent parent;  
}
如果我有一个父级P和指向P的子级C1...Cn,是否有一种干净而漂亮的JPA方式,可以在删除P时自动删除子级C1...Cn(即entityManager.remove(P))?
我正在寻找类似于SQL中的ON DELETE CASCADE的功能。

1
即使只有“Child”引用了“Parent”(以这种方式引用是单向的),您是否为将“Child”的列表添加到“Parent”中使用“@OneToMany”映射和“Cascade = ALL”属性感到困惑?我认为JPA应该能够解决这个问题,即使只有一侧持有引用。 - kvDennis
1
@kvDennis,有些情况下,您不希望将多侧紧密耦合到单侧。例如,在类似ACL的设置中,安全权限是透明的“附加功能”。 - Bachi
如果您正在使用Spring Boot 3,请查看此答案: https://dev59.com/YVcP5IYBdhLWcg3wVYeh#70427325 - pyfyc
7个回答

133

如果你正在使用Hibernate作为你的JPA提供者,你可以使用注解@OnDelete。这个注解会给关系添加触发器ON DELETE CASCADE,将子项的删除委托给数据库。

示例:

public class Parent {
   
        @Id
        private long id;

}


public class Child {
        
        @Id
        private long id;
  
        @ManyToOne
        @OnDelete(action = OnDeleteAction.CASCADE)
        private Parent parent;
}
     
使用这种解决方案,从子级到父级的单向关系就足以自动删除所有子级。此解决方案不需要任何监听器等。而像DELETE FROM Parent WHERE id = 1这样的JPQL查询将会删除子级。

6
我无法用这种方式使它工作,是否有特定版本的Hibernate或其他更详细的示例类似于这种情况? - Mardari
4
很难说为什么对你不起作用。要使其有效,您可能需要重新生成模式或手动添加级联删除。@OnDelete注释已经存在一段时间了,因此我不认为版本是问题的原因。 - Thomas Hunziker
14
谢谢回答。简要说明:只有当您通过hibernate启用DDL生成时,数据库级联触发器才会被创建。否则,您必须以另一种方式添加它(例如liquibase),以允许像“DELETE FROM Parent WHERE id = 1”这样的即席查询直接针对数据库执行级联删除。 - mjj1409
1
当关联是 @OneToOne 时,这个不起作用。有什么想法如何使用 @OneToOne 解决它? - stakowerflol
1
@ThomasHunziker 这对于孤儿删除不起作用,对吗? - oxyt
显示剩余3条评论

85

JPA中的关系始终是单向的,除非您在两个方向上将父项与子项相关联。从父项到子项的级联REMOVE操作需要从父项到子项(而不仅仅是相反方向)的关系。

因此,您需要执行以下操作:

  • 要么将单向的@ManyToOne关系改为双向的@ManyToOne或单向的@OneToMany。然后,您可以级联REMOVE操作,以便EntityManager.remove将删除父项和子项。您还可以将orphanRemoval指定为true,在将父集合中的子实体设置为null时删除任何孤立的子实体,即在任何父集合中都不存在的情况下删除子实体。
  • 或者,在子表中指定外键约束为ON DELETE CASCADE。在调用EntityManager.remove(parent)后,您需要调用EntityManager.clear(),因为持久化上下文需要刷新-在数据库中删除子实体后,它们不应该存在于持久化上下文中。

10
有没有一种使用JPA注解来执行No2的方法? - user2573153
4
如何使用Hibernate XML映射执行No2操作? - arg20
有的,可以在Child类中的private Parent parent;上添加以下内容:@JoinColumn(name = "parentId", foreignKey = @ForeignKey(value = CONSTRAINT, foreignKeyDefinition = "FOREIGN KEY (parentId) REFERENCES parent(id) ON DELETE CASCADE")) - Bram Janssens

16
创建一个双向关系,就像这样:
@Entity
public class Parent implements Serializable {

    @Id
    @GeneratedValue
    private long id;

    @OneToMany(mappedBy = "parent", cascade = CascadeType.REMOVE)
    private Set<Child> children;
}

13
糟糕的答案,JPA中的双向关系很糟糕,因为操作大量子集需要花费极长的时间。 - Enerccio
2
双向关系慢的证据在哪里? - shalama
1
@enerccio 如果双向关系是一对一的呢?此外,请展示一篇说明双向关系慢的文章?慢在什么方面?检索?删除?更新? - saran3h
2
@Enerccio 我认为每个人都在连接中使用惰性加载。那么为什么它仍然是一个性能问题呢? - saran3h
1
@saran3h 懒加载/急加载只有在列表被加载时才会改变动态,急加载会在实体被加载时立即加载它,而懒加载则在第一次访问时加载。问题是,即使您只是向懒加载的列表中添加新值,该列表也将被加载,这意味着即使是简单的添加新子项也需要进行 N 次数据库操作,这就是为什么我放弃了 OneToMany 操作,而是使用原始映射 id,如果我知道集合将很大的话。 - Enerccio
显示剩余4条评论

3
我曾经在单向@ManyToOne中发现,删除不如预期那样工作。 当删除父对象时,理想情况下子对象也应该被删除,但是只有父对象被删除了,而子对象没有被删除,成为了孤儿。
所使用的技术是Spring Boot/Spring Data JPA/Hibernate。
Sprint Boot版本:2.1.2.RELEASE。
Spring Data JPA/Hibernate用于删除行。例如: parentRepository.delete(parent) ParentRepository扩展了标准的CRUD存储库,如下所示: ParentRepository extends CrudRepository 以下是我的实体类。
@Entity(name = “child”)
public class Child  {

    @Id
    @GeneratedValue
    private long id;

    @ManyToOne( fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = “parent_id", nullable = false)
    @OnDelete(action = OnDeleteAction.CASCADE)
    private Parent parent;
}

@Entity(name = “parent”)
public class Parent {

    @Id
    @GeneratedValue
    private long id;

    @Column(nullable = false, length = 50)
    private String firstName;


}

我找到了“删除”操作不能正常工作的解决方案。显然,Hibernate没有使用mysql引擎-INNODB。在mysql中生成外键约束需要INNODB引擎。在application.properties中使用以下属性,可以使spring boot/hibernate使用mysql引擎INNODB,因此外键约束起作用,从而也可以进行级联删除。 - ranjesh
之前的评论中错过了属性使用。以下是使用的Spring属性:spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect``` - ranjesh
提醒一下,你的代码中有错的双引号。请查看 name="parent" - alexander

1

你不需要使用双向关联来替换你的代码,只需在ManyToOne注释中添加CascaType.Remove属性,然后使用@OnDelete(action = OnDeleteAction.CASCADE),这对我来说很有效。


0
使用这种方式仅删除一侧。
    @ManyToOne(cascade=CascadeType.PERSIST, fetch = FetchType.LAZY)
//  @JoinColumn(name = "qid")
    @JoinColumn(name = "qid", referencedColumnName = "qid", foreignKey = @ForeignKey(name = "qid"), nullable = false)
    // @JsonIgnore
    @JsonBackReference
    private QueueGroup queueGroup;

-1

@Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)

给定的注释对我有用。可以试一试

例如:

     public class Parent{
            @Id
            @GeneratedValue(strategy=GenerationType.AUTO)
            @Column(name="cct_id")
            private Integer cct_id;
            @OneToMany(cascade=CascadeType.REMOVE, fetch=FetchType.EAGER,mappedBy="clinicalCareTeam", orphanRemoval=true)
            @Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
            private List<Child> childs;
        }
            public class Child{
            @ManyToOne(fetch=FetchType.EAGER)
            @JoinColumn(name="cct_id")
            private Parent parent;
    }

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