简体   繁体   中英

One to Many relationship JPA/Hibernate removing links

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!

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].
// 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.

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.

这个问题是有关的: JPA CascadeType.ALL不会删除孤儿

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM