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


95

假设我有如下所示的单向 @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的子C 1 ... C n,那么在JPA中是否有一种干净漂亮的方法来在删除P(即)时自动删除子C 1 ... C nentityManager.remove(P)

我正在寻找的功能类似于ON DELETE CASCADESQL。


1
即使只有“孩子”具有对“父母”的引用(以这种方式,引用也是单向的),您还是很难将带有“ @OneToMany”映射和“ Cascade = ALL”属性的“孩子”列表添加到“父母”?我认为JPA应该解决,即使只有坚强的一方也能保持参考。
kvDennis 2011年

1
@kvDennis,在某些情况下,您不想将多面紧密结合在一起。例如,在ACL-等,其中安全权限设置透明的“附加”
八尺

Answers:


73

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

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

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

7
有没有办法用JPA注释进行No2?
user2573153 2014年

3
如何使用Hibernate xml映射进行No2?
arg20 2014年

92

如果您将休眠用作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这样的查询也将删除子级。


4
我无法通过这种方式工作,是否有任何特定版本的休眠或类似这样的其他更详细的示例?
Mardari

3
很难说为什么它对您不起作用。要使此工作正常进行,您可能需要重新生成架构,或者必须手动添加级联删除。@OnDelete批注似乎已经存在了一段时间,因此我不认为该版本是个问题。
Thomas Hunziker

10
感谢你的回答。快速说明:仅当您通过休眠启用了DDL生成时,才会创建数据库级联触发器。否则,您将不得不添加它另一种方式(例如liquibase),以允许直接针对数据库运行即席查询,例如“从父级WHERE id = 1删除”执行级联删除。
mjj1409 '17

1
当关联为时,此方法不起作用。有@OneToOne任何想法如何解决@OneToOne
stakowerflol

1
@ThomasHunziker这不适用于orphanRemoval吧?
oxyt

13

创建双向关系,如下所示:

@Entity
public class Parent implements Serializable {

    @Id
    @GeneratedValue
    private long id;

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

8
错误的答案,在JPA中双向关系非常糟糕,因为在大型儿童
玩具

1
有证据表明双向关系很慢吗?
shalama

@enerccio如果双向关系是一对一的怎么办?另外,请显示一篇文章,指出双向关系很慢?慢什么?正在检索?删除?更新中?
saran3h

@ saran3h每个操作(添加,删除)都将加载所有子项,因此这可能是无用的巨大数据加载(例如添加值不需要从数据库加载所有子项,这正是此映射的作用)。
Enerccio

@Enerccio我认为每个人都在联接上使用延迟加载。那么它仍然是性能问题吗?
saran3h

1

我在单向@ManytoOne中看到过,删除无法正常工作。删除父级后,理想情况下也应删除子级,但仅删除父级,不删除子级,而将其保留为孤儿

使用的技术是Spring Boot / Spring Data JPA / Hibernate

Sprint引导程序:2.1.2.RELEASE

Spring Data JPA / Hibernate用于删除行.eg

parentRepository.delete(parent)

ParentRepository扩展了标准的CRUD存储库,如下所示 ParentRepository extends CrudRepository<T, ID>

以下是我的实体课

@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;


}

我找到了为什么删除不起作用的解决方案。显然,休眠状态未使用mysql Engine -INNODB,您需要mysql的引擎INNODB才能生成外键约束。在application.properties中使用以下属性,可使spring boot / hibernate使用mysql引擎INNODB。因此,外键约束起作用,因此也删除了层叠
ranjesh

先前评论中缺少的属性。以下是使用的弹簧属性spring.jpa.hibernate.use-new-id-generator-mappings=true spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
ranjesh

仅供参考,您"的代码有误。请参阅name= "parent"
亚历山大

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;
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.