Extra uninitialised entities spawned on merge()

#1

In the following, Element is a base entity class, with a bi-directional @ManyToOne-@OneToMany relationship Element owner - List<Element> ownedElements, and Example is a subclass entity with a relationship @OneToOne Element child.

package test;

import java.util.*;
import javax.persistence.*;

public class TestObjectdbSpawnsDuplicatesOnMerge {

    final static boolean DO_PERSIST_BEFORE_MERGE = true;

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {

        EntityManagerFactory emf =
                Persistence.createEntityManagerFactory("objectdb:$objectdb/db/test.tmp;drop");
        EntityManager em = emf.createEntityManager();

        em.getTransaction().begin();
        Element root = new Element(null, "root");
        em.persist(root);
        em.getTransaction().commit();
        Long id = root.getId();

        root = em.find(Element.class, id); //now detached, since outside transaction
        Example e = new Example(root, "example");
        root.addOwnedElement(e);
       
        if (DO_PERSIST_BEFORE_MERGE) {
            em.getTransaction().begin();
            em.persist(e);
            em.getTransaction().commit();
        }

        em.getTransaction().begin();
        em.merge(root);
        em.getTransaction().commit();

        TypedQuery<Element> query = em.createQuery("SELECT e FROM Element e", Element.class);
        List<Element> results = query.getResultList();
        for (Element result : results) {
            System.out.println("\nresult:" + result);
            for (Element owned : result.getOwnedElements()) {
                System.out.println("owned: " + owned);
            }
        }

        em.close();
        emf.close();
    }

    @Entity
    public static class Element {

        private String name;

        public Element() {
        }

        public Element(Element owner, String name) {
            this.owner = owner;
            this.name = name;
        }

        @Override
        public String toString() {
            return "{" + getClass().getSimpleName() + "}[" + id + "](" + name + ")";
        }

        private Long id;

        @Id
        @GeneratedValue
        public Long getId() {
            return id;
        }

        public void setId(Long id) {
            this.id = id;
        }

        private Element owner;
        @ManyToOne()
        public Element getOwner() {
            return owner;
        }
        public void setOwner(Element owner) {
            this.owner = owner;
        }

        private List<Element> ownedElements = new ArrayList<Element>();
        @OneToMany(mappedBy = "owner",
        cascade = CascadeType.ALL
        )
        public List<Element> getOwnedElements() {
            return ownedElements;
        }
        public void setOwnedElements(List<Element> ownedElements) {
            this.ownedElements = ownedElements;
        }
        public boolean addOwnedElement(Element owned) {
            return ownedElements.add(owned);
        }
    }

    @Entity
    static public class Example extends Element {

        public Example() {
            init();
        }

        public Example(Element owner, String name) {
            super(owner, name);
            init();
        }

        private void init() {
            child = new Element(this, "child");
            addOwnedElement(child);
        }

        private Element child;

        @OneToOne
        public Element getChild() {
            return child;
        }

        public void setChild(Element child) {
            this.child = child;
        }
    }

}

 

Case: DO_PERSIST_BEFORE_MERGE = true: If I create an Element 'root', persist it, load 'root' again with find() outside a transaction as a detached entity, then create an Example 'e' (with 'root' as owner), persist it, then add it as an ownedElement of 'root', then merge 'root', all is fine, and the output is as expected:

result:{Element}[1](root)
owned: {Example}[2](example)

result:{Example}[2](example)
owned: {Element}[3](child)

result:{Element}[3](child)

Case: DO_PERSIST_BEFORE_MERGE = false: If I create an Element 'root', persist it, load 'root' again with find() outside a transaction as a detached entity, then create an Example 'e' (with 'root' as owner), do NOT persist it, then add it as an ownedElement of 'root', then merge 'root', the output shows a number of additional persistent entities that are also not fully initialised (have for example null 'name' String):

result:{Element}[1](root)
owned: {Example}[4](example)

result:{Example}[2](null)
owned: {Element}[5](child)

result:{Element}[3](null)

result:{Example}[4](example)
owned: {Element}[6](child)

result:{Element}[5](child)

result:{Element}[6](child)

I would like to understand why the merge() on the root element is not able to correctly persist the related Example entity cleanly and why these additional entities arise with ObjectDB.

Pro JPA2 says p.161:

"When merge() is invoked on a new entity, it behaves similarly to the persist() operation. It adds the entity to the persistence context, but instead of adding the original entity instance, it creates a new copy and manages that instance instead. The copy that is created is by the merge() operation is persisted as if the persist() operation were invoked on it."

"In the presence of relationships, the merge() operation will attempt to update the managed entity to point to managed versions of the entities referenced by the detached entity. If the entity has a relationship to an object that has no persistent identity, the outcome of the merge operation is undefined. Some providers might allow the managed copy to point to the non-persistent object, whereas others might thown an exception immediately. The merge() operation can be optionally cascaded in these cases to prevent an exception from occurring."

Grateful for feedback (although the problem is somewhat academic as a solution is clear),

Webel

#2

Thank you for this bug report. Build 2.4.0_05 fixes it.

You have to set the name field as persistent (by adding a property) in order to get the correct output:

result:{Element}[1](root)
owned: {Example}[2](example)

result:{Example}[2](example)
owned: {Element}[3](child)

result:{Element}[3](child)

 

ObjectDB Support
#3

Tested. I can confirm that Build 2.4.0_05 also fixes a similar duplicate-on-merge problem I was encountering in my full web application. Thanks.

Reply