One to Many relationship JPA/Hibernate removing links

10,943

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

Solution 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.

Share:
10,943
hello
Author by

hello

Updated on June 09, 2022

Comments

  • hello
    hello almost 2 years

    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!