JPA/Hibernate中的一对多关系删除链接。

3

我已经设置了如下双向关系:

class Child{
    @ManyToOne
    @JoinTable(name = "CHILDREN_WITH_PARENT", 
            joinColumns = {@JoinColumn(name = "CHILD_ID")}, 
            inverseJoinColumns = {@JoinColumn(name = "PARENT_ID")}
    )
    private Parent parent;
}

class Parent{
    @OneToMany(mappedBy="parent", cascade=CascadeType.ALL)
    Set<Child> childrens = new HashSet<Child>();

    public void persistOrMerge() {
        EntityManager em = entityManager();
        em.getTransaction().begin();
        try {
            if (em.contains(this))
                return;
            if (id == null || id == 0) {
                this.setCreatedDate(new Date());
                em.persist(this);
            } else {
                Parent prev = em.find(Parent.class, this.id);
                if (prev == null) {
                    em.persist(this);
                } else{
                    this.setCreatedDate(new Date());
                    em.merge(this);
                }
            }
            em.flush();
            em.getTransaction().commit();

        }  finally {
            em.close();

        }
    }

}

在我的客户端代码中,我有以下代码(GWT + EntityProxy):
Set<ChildProxy> children  = new HashSet<ChildProxy>();
if(childIsNew)
   child = request.create(Children.class)
else
   child = request.edit(oldChild)
children.add(child);
//If children are deleted, they are not contained in the set
//we are sending back to server
parent.setChildren(children)
parent.persistOrMerge();

这段代码只能用于添加新的子元素。即使父类接收了一个空的子元素集,也无法从父类中删除子元素。JOIN表中的关联链接也不会被删除。

请问我错过了什么吗?

谢谢!


我的理解是,由于对象由EntityManager管理,它将删除在接收到的新集合中不存在的子项。 - hello
你能展示一下setChildren的确切作用吗?我没有发现任何错误。 - atamanroman
setChildren是一个简单的访问器方法。 - hello
public void setChildren(Set<Child> children) { this.children = children; }请将孩子的集合设置为:public void setChildren(Set<Child> children) { this.children = children; } - hello
4个回答

6

首先,实体直接使用实体管理器是一个非常糟糕的想法。

EntityManager.merge()方法返回实际合并后的实例,这意味着在您的代码中,当您执行以下操作时:

em.merge(this)

您无法保证合并后的实例仍然对应于“this”,从那时起,您可能会看到各种逻辑问题。

如果您认为这不是什么大问题,可以通过在OneToMany关系的孩子方面启用孤儿删除来解决您的问题,前提是这些孩子没有在其他关系中被使用。否则,您将不得不手动合并。

@OneToMany(mappedBy="parent", cascade=CascadeType.ALL, orphanRemoval=true)
Set<Child> childrens = new HashSet<Child>();

JPA 2.0规范规定:

被指定为OneToOne或OneToMany的关联支持使用orphanRemoval选项。当orphanRemoval生效时,以下行为将应用:

  • 如果从关系中移除了作为关系目标的实体(通过将关系设置为null或从关系集合中删除实体),则删除操作将应用于即将成为孤儿的实体。删除操作在刷新操作时应用。orphanRemoval功能旨在用于由其父实体私有“拥有”的实体。可移植的应用程序否则不应依赖于特定的删除顺序,并且不得重新分配已成为孤儿的实体到另一个关系或以其他方式尝试将其持久化。如果被孤立的实体是一个游离、新建或已删除的实体,则orphanRemoval的语义不适用。
  • 如果将删除操作应用于托管源实体,则删除操作将根据第3.2.3节的规则级联到关系目标上(因此不需要为关系指定cascade=REMOVE)[20]。

我理解你想表达的意思,但这仍然是一个非持久层问题。另一方面,在我的情况下,即使底层的MySQL表包含应该被删除的链接。 - hello
如果这不是问题的根源,你可以在关系的@OneToMany一侧打开orphanRemoval来解决它。 - Edwin Dalorzo
即使父级对象已经删除,子级对象也应该存在。如果我理解有误,请纠正我,但我认为在我的情况下,使用orphanRemoval=true不是合适的解决方案。我想做的只是删除链接。我的理解是EntityManager会自动处理这个问题。看来我必须手动处理了。 - hello

1
// In the entity class of Parent1 write a method to unlink parent2
public void unLinkParent2(Parent2 parent2)
{
    //remove columns from the link table, pertaining to the parent2
    getParent2Collection().remove(parent2);
    // using the parent2 object remove 'this' parent1 entity link columns
    parent2.getParent1Collection().remove(this);
}

父母1: 父母2: 链接P1-P2 -------------------------------------------------- Id1(主键) Id2(主键) Id1(复合主键) name1 Name2 Id2(复合主键) 表中的Id1Id2一起是一个主键,引用了和表。


1

ManyToOne映射控制关系 - 您已将OneToMany标记为子项与其父项的关系。这意味着只有从子项方面合并才能捕获关系的更改。因为从父项列表中删除的子项不再存在于其中,所以无法级联合并到它们,并且其中任何更改都不会持久保存到数据库中。

正如其他人指出的那样,孤儿删除将允许在合并时删除列表中不存在的任何子项。当您想要将关系置为空时,这可能过于复杂。在这种情况下,您可以反转关系,使其由父项拥有。这将确保关系得到更新,但是已删除的子项中的任何更改仍将不被捕获。获取这些更改的唯一方法是将子项添加到被合并的新父项中,或者独立地合并它们。如果它们可以没有父项存在,则直接调用persist/merge是更好的选择。


0

我没有收到任何异常。表是由EntityManager在初始化时创建的。 - hello
删除父对象会同时删除子对象,因此我不确定是否与CascadeType.ALL未删除孤儿有关。 - hello
如果我理解正确的话,将父对象对子对象的引用设置为null与删除子对象(em.remove(child))是不同的。所以这可能是相关的,可以试一下。顺便说一句,调用em.remove(oldChild)应该可以解决你的问题。 - atamanroman

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