One to Many relationship JPA/Hibernate removing links

吃可爱长大的小学妹 提交于 2020-01-15 05:03:07

问题


I have bidirectional relationship setup as follows:

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();

        }
    }

}

On my client side I have following code (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();

This code only works for adding new children. Removing of children from parent does not work even if parent class receives an empty children set. The linkages in JOIN table are not removed.

Can you please tell where I am missing something?

Thanks!


回答1:


I will start by saying that is a really bad idea that the entity is the one using the entity manager directly.

The EntityManager.merge() method returns the actual merged instance, this implies that in your code, when you issue

em.merge(this)

You have no guarantee whatsoever that the merged instance corresponds to "this" anymore, and from that point on you may see all kinds of logical problems.

If you do not think this is such a big deal, your problem should be solved by turning on orphan removal on the OneToMany side of the relationship, provided that the children are not being used anywhere else in other relationships. Otherwise you will have to do the merging manually.

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

The JPA 2.0 specification states that

Associations that are specified as OneToOne or OneToMany support use of the orphanRemoval option. The following behaviors apply when orphanRemoval is in effect:

  • If an entity that is the target of the relationship is removed from the relationship (by setting the relationship to null or removing the entity from the relationship collection), the remove operation will be applied to the entity being orphaned. The remove operation is applied at the time of the flush operation. The orphanRemoval functionality is intended for entities that are privately "owned" by their parent entity. Portable applications must otherwise not depend upon a specific order of removal, and must not reassign an entity that has been orphaned to another relationship or otherwise attempt to persist it. If the entity being orphaned is a detached, new,or removed entity, the semantics of orphanRemoval do not apply.
  • If the remove operation is applied to a managed source entity, the remove operation will be cascaded to the relationship target in accordance with the rules of section 3.2.3, (and hence it is not necessary to specify cascade=REMOVE for the relationship)[20].



回答2:


// 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);
}
Parent1:        Parent2:        LinkP1-P2
--------------------------------------------------
Id1(PK)         Id2(PK)         Id1 (composite PK)
name1           Name2           Id2 (composite PK)

Id1 and Id2 of the Link table together is a primary key referring to Parent1 and Parent2 tables.




回答3:


This question is related: JPA CascadeType.ALL does not delete orphans




回答4:


The ManyToOne mapping controls the relationship - you have marked the OneToMany as mappedby the child's relation to its parent. This means that changes to the relationship are only picked up if merged from the child side. Because children removed from the parents list are no longer in it, merge cannot be cascaded to them, and any changes in them are never persisted to the database.

As others pointed out, orphan removal will allow for any children not in the list to be deleted on merge. This might be overkill when all you want is the relationship nulled out. In this scar, you can probably reverse the relationship so that it is owned by the parent. That will ensure the relation gets updated, but any changes in removed children will still not get picked up. The only way to get those changes picked up is to add the children to a new parent that gets merged, or to merge them independently. If they can exist without a parent, calling persist/merge on them directly is the better option.



来源:https://stackoverflow.com/questions/5641690/one-to-many-relationship-jpa-hibernate-removing-links

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!