【问题标题】:How does JPA orphanRemoval=true differ from the ON DELETE CASCADE DML clauseJPA orphanRemoval=true 与 ON DELETE CASCADE DML 子句有何不同
【发布时间】:2011-05-18 19:00:51
【问题描述】:

我对 JPA 2.0 orphanRemoval 属性有点困惑。

当我使用我的 JPA 提供程序的数据库生成工具创建底层数据库 DDL 以在特定关系上具有 ON DELETE CASCADE 时,我认为我可以看到它是必需的。

但是,如果数据库存在并且关系上已经有一个ON DELETE CASCADE,这是否不足以适当地级联删除? orphanRemoval 还有什么作用?

干杯

【问题讨论】:

    标签: hibernate jpa cascade cascading-deletes orphan-removal


    【解决方案1】:

    orphanRemovalON DELETE CASCADE无关。

    orphanRemoval 是一个完全ORM 特定的东西。当不再从“父”实体引用时,它标记要删除的“子”实体,例如当您从父实体的相应集合中删除子实体时。

    ON DELETE CASCADE数据库特有的东西,当“父”行被删除时,它会删除数据库中的“子”行。

    【讨论】:

    • 这是否意味着它们具有安全效果,但不同的系统负责实现它?
    • 匿名,它没有相同的效果。 ON DELETE CASCADE 告诉数据库在删除父记录时删除所有子记录。也就是说,如果我删除 INVOICE,然后删除该 INVOICE 上的所有项目。 OrphanRemoval 告诉 ORM,如果我从属于 Invoice 对象(在内存操作中)的 Items 集合中删除一个 Item 对象,然后“保存”该 Invoice,则应从底层 DB 中删除删除的 Item。
    • 如果你使用单向关系,那么即使你没有设置orphanRemoval=true,也会自动移除孤儿
    【解决方案2】:

    一个例子取自here:

    当一个Employee 实体对象被移除时,移除操作被级联到被引用的Address 实体对象。在这方面orphanRemoval=truecascade=CascadeType.REMOVE是相同的,如果指定orphanRemoval=trueCascadeType.REMOVE是多余的。

    这两种设置的区别在于对断开关系的响应。例如,将地址字段设置为null 或另一个Address 对象时。

    • 如果指定了orphanRemoval=true,则断开连接的Address 实例 被自动删除。这对于清理依赖项很有用 如果没有来自的引用,则不应存在的对象(例如Address) 一个所有者对象(例如Employee)。

    • 如果仅指定cascade=CascadeType.REMOVE,则无自动操作 被采取,因为断开关系不是删除 操作。

    为避免因孤立删除而导致悬空引用,应仅对包含私有非共享依赖对象的字段启用此功能。

    我希望这会让它更清楚。

    【讨论】:

    • 阅读您的回答后,我意识到它们之间的确切区别并且我的问题已经解决。如果这些子实体与父实体中定义的集合断开连接(删除),我会陷入从数据库中删除子实体的问题。同样,我问了问题'stackoverflow.com/questions/15526440/…'。只需添加我的评论以链接这两个问题。
    【解决方案3】:

    从集合中删除子实体的那一刻,您也将从数据库中删除该子实体。 orphanRemoval 也意味着你不能改变父母;如果有一个部门有员工,一旦您删除该员工并将其放入另一个部门,您将在刷新/提交时无意中将该员工从数据库中删除(以先到者为准)。士气是将 orphanRemoval 设置为 true,只要您确定该父级的子级在其存在期间不会迁移到其他父级。开启 orphanRemoval 也会自动将 REMOVE 添加到级联列表中。

    【讨论】:

    • 完全正确...也称为“私人”父/子关系。
    • 这意味着只要我调用department.remove(emp);,该员工就会从emp表中删除,甚至不需要调用commit()
    【解决方案4】:

    实体状态转换

    JPA 将实体状态转换转换为 SQL 语句,例如 INSERT、UPDATE 或 DELETE。

    当您 persist 一个实体时,您正在安排 INSERT 语句在 EntityManager 自动或手动刷新时执行。

    当您remove 一个实体时,您正在调度 DELETE 语句,该语句将在刷新持久性上下文时执行。

    级联实体状态转换

    为方便起见,JPA 允许您将实体状态转换从父实体传播到子实体。

    因此,如果您有一个父 Post 实体与 @OneToMany 关联与 PostComment 子实体:

    Post实体中的comments集合映射如下:

    @OneToMany(
        mappedBy = "post", 
        cascade = CascadeType.ALL,
        orphanRemoval = true
    )
    private List<Comment> comments = new ArrayList<>();
    

    CascadeType.ALL

    cascade 属性告诉 JPA 提供者将实体状态转换从父 Post 实体传递到 comments 集合中包含的所有 PostComment 实体。

    因此,如果您删除 Post 实体:

    Post post = entityManager.find(Post.class, 1L);
    assertEquals(2, post.getComments().size());
    
    entityManager.remove(post);
    

    JPA 提供者将首先删除PostComment 实体,当所有子实体都被删除时,它也会删除Post 实体:

    DELETE FROM post_comment WHERE id = 1
    DELETE FROM post_comment WHERE id = 2
    
    DELETE FROM post WHERE id = 1
    

    孤儿删除

    当您将orphanRemoval 属性设置为true 时,JPA 提供程序将在从集合中删除子实体时安排remove 操作。

    所以,在我们的例子中,

    Post post = entityManager.find(Post.class, 1L);
    assertEquals(2, post.getComments().size());
    
    PostComment postComment = post.getComments().get(0);
    assertEquals(1L, postComment.getId());
    
    post.getComments().remove(postComment);
    

    JPA 提供程序将删除关联的 post_comment 记录,因为 PostComment 实体不再在 comments 集合中引用:

    DELETE FROM post_comment WHERE id = 1
    

    关于删除级联

    ON DELETE CASCADE 在 FK 级别定义:

    ALTER TABLE post_comment 
    ADD CONSTRAINT fk_post_comment_post_id 
    FOREIGN KEY (post_id) REFERENCES post 
    ON DELETE CASCADE;
    

    一旦你这样做,如果你删除 post 行:

    DELETE FROM post WHERE id = 1
    

    数据库引擎会自动删除所有关联的post_comment 实体。但是,如果您错误地删除了根实体,这可能是一个非常危险的操作。

    结论

    JPA cascadeorphanRemoval 选项的优点是您还可以从乐观锁定中受益,以防止丢失更新。

    如果您使用 JPA 级联机制,则无需使用 DDL 级别的ON DELETE CASCADE,如果您移除在多个级别上有许多子实体的根实体,这可能是非常危险的操作。

    【讨论】:

    • 所以在孤儿删除部分你的答案:post.getComments().remove(postComment);仅由于 Persist 级联,将在 OneToMany 双向映射中工作。如果没有级联并且没有在 ManyToOne 端进行删除,例如在您的示例中,删除 2 个实体之间的连接将不会保留在 DB 中?
    • 孤儿删除不受CascadeType 的影响。这是一个互补的机制。现在,您将删除误认为是持久化。孤立删除是关于删除未引用的关联,而持久是关于保存新实体。您需要按照答案中提供的链接来更好地理解这些概念。
    • 我不明白一件事:如果我们从不删除 M 端的连接,孤儿删除将如何在双向映射中发挥作用?我认为从 Post 的列表中删除 PostComment 而不将 PostComment.post 设置为 null 不会导致删除数据库中这两个实体之间的连接。这就是为什么我认为孤儿删除不会起作用的原因,在关系世界中 PostComment 不是孤儿。有空我会测试一下。
    • 我添加了these two examples in my High-Performance Java Persistence GitHub repository,它演示了它是如何工作的。您不需要像通常直接删除实体那样同步子端。但是,孤儿删除仅在添加级联时才有效,但这似乎是 Hibernate 限制,而不是 JPA 规范。
    【解决方案5】:

    DDL ON DELETE CASCADE 的等效 JPA 映射是 cascade=CascadeType.REMOVE。孤立删除意味着当与其“父”实体的关系被破坏时,依赖实体被删除。例如,如果从@OneToMany 关系中删除了一个孩子,而没有在实体管理器中明确删除它。

    【讨论】:

    • cascade=CascadeType.REMOVE 不等同于 ON DELETE CASCADE。在应用程序代码中删除并且不影响 DDL,其他在 DB 中执行。见stackoverflow.com/a/19696859/548473
    【解决方案6】:

    区别在于:
    - orphanRemoval = true:“子”实体在不再被引用时被删除(其父实体可能不会被删除)。
    - CascadeType.REMOVE:“子”实体仅在其“父”被移除时才会被移除。

    【讨论】:

      【解决方案7】:

      @GaryK 的回答非常棒,我花了一个小时寻找orphanRemoval = trueCascadeType.REMOVE 的解释,它帮助我理解了。

      总结:orphanRemoval = trueCascadeType.REMOVE 工作方式相同仅当我们删除对象 (entityManager.delete(object)) 并且我们希望子对象也被删除。

      在完全不同的情况下,当我们获取一些像List&lt;Child&gt; childs = object.getChilds() 这样的数据,然后使用orphanRemoval=true 删除一个孩子(entityManager.remove(childs.get(0))时,会导致对应于childs.get(0) 的实体将从数据库中删除。

      【讨论】:

      • 第二段有错字:没有 entityManager.delete(obj); 这样的方法它是 entityManager.remove(obj)。
      【解决方案8】:

      在以下情况下,孤儿删除与 ON DELETE CASCADE 具有相同的效果:- 假设我们在学生实体和指南实体之间有一个简单的多对一关系,其中许多学生可以映射到同一个指南,并且在数据库中,我们在学生和指南表之间有一个外键关系,因此学生表的 id_guide 为 FK .

          @Entity
          @Table(name = "student", catalog = "helloworld")
          public class Student implements java.io.Serializable {
           @Id
           @GeneratedValue(strategy = IDENTITY)
           @Column(name = "id")
           private Integer id;
      
          @ManyToOne(cascade={CascadeType.PERSIST,CascadeType.REMOVE})
          @JoinColumn(name = "id_guide")
          private Guide guide;
      

      //父实体

          @Entity
          @Table(name = "guide", catalog = "helloworld")
          public class Guide implements java.io.Serializable {
      
      /**
       * 
       */
      private static final long serialVersionUID = 9017118664546491038L;
      
      @Id
      @GeneratedValue(strategy = IDENTITY)
      @Column(name = "id", unique = true, nullable = false)
      private Integer id;
      
      @Column(name = "name", length = 45)
      private String name;
      
      @Column(name = "salary", length = 45)
      private String salary;
      
      
       @OneToMany(mappedBy = "guide", orphanRemoval=true) 
       private Set<Student> students = new  HashSet<Student>(0);
      

      在这种情况下,关系是这样的,学生实体是关系的所有者,因此我们需要保存学生实体以保存整个对象图,例如

          Guide guide = new Guide("John", "$1500");
          Student s1 = new Student(guide, "Roy","ECE");
          Student s2 = new Student(guide, "Nick", "ECE");
          em.persist(s1);
          em.persist(s2);
      

      这里我们用两个不同的学生对象映射同一个指南,由于使用了 CASCADE.PERSIST,对象图将如下保存在数据库表中(在我的例子中是 MySql)

      STUDENT 表:-

      ID 名称部门 Id_Guide

      1     罗伊     ECE    1

      2    尼克    ECE    1

      指南表:-

      身份证姓名工资

      1    约翰     1500 美元

      现在,如果我想删除其中一名学生,请使用

            Student student1 = em.find(Student.class,1);
            em.remove(student1);
      

      当一个学生记录被删除时,相应的向导记录也应该被删除,这就是学生实体中的 CASCADE.REMOVE 属性出现的地方,它的作用是;它删除了标识符为 1 的学生以及相应的向导对象(标识符 1)。但是在这个例子中,还有一个学生对象映射到同一个指南记录,除非我们在指南实体中使用 orphanRemoval=true 属性,否则上面的删除代码将不起作用。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-10-20
        • 2013-01-30
        • 2019-08-11
        • 1970-01-01
        • 2012-07-16
        • 2019-09-26
        • 2019-07-12
        • 2015-02-12
        相关资源
        最近更新 更多