[hibernate-issues] [Hibernate-JIRA] Commented: (HHH-3332) Hibernate duplicate then child entity's on merge

Trond Isaksen (JIRA) noreply at atlassian.com
Tue May 18 08:03:11 EDT 2010


    [ http://opensource.atlassian.com/projects/hibernate/browse/HHH-3332?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=37186#action_37186 ] 

Trond Isaksen commented on HHH-3332:
------------------------------------

I believe the problem is that when merging from a child C1 to a parent P, the cascade is done BEFORE the save, which means that the parent will actually do a cascade back to the child C1 first, which will then be inserted. After the parent has been processed, the first C1 merge()-call will insert the child again. I believe that exchanging the DefaultMergeEventListener with the following fixes the problem:

public class ModifiedIdTransferringMergeEventListener extends DefaultMergeEventListener {

    private static final long serialVersionUID = 1L;
    static ThreadLocal<IdentitySet> transientObjects = new ThreadLocal<IdentitySet>();

    /**
     * Hibernate 3.1 implementation of ID transferral.
     */
    protected void entityIsTransient(MergeEvent event, Map copyCache) {
        boolean topLevelInvocation = transientObjects.get() == null;
        if (topLevelInvocation) {
            transientObjects.set(new IdentitySet());
        }
        if (transientObjects.get().contains(event.getEntity()))
            return;
        transientObjects.get().add(event.getEntity());
        super.entityIsTransient(event, copyCache);

        //Start IdUpdating
        SessionImplementor session = event.getSession();
        EntityPersister persister = session.getEntityPersister(event.getEntityName(), event.getEntity());
        // Extract id from merged copy (which is currently registered with Session).
        Serializable id = persister.getIdentifier(event.getResult(), session.getEntityMode());
        // Set id on original object (which remains detached).
        persister.setIdentifier(event.getOriginal(), id, session.getEntityMode());
        //End IdUpdating

        if (topLevelInvocation) {
            transientObjects.get().clear();
            transientObjects.set(null);
        }
    }

}

The transientObject threadlocal just remembers the objects that are going to be inserted, and stop cascading from reaching them again. A better implementation would obviously be to send the map with "inflight" objects as a parameter instead of using a threadlocal. Please note that this implementation also contains the code from the IdUpdatingMergeEventListener(Spring), which updates the id of merging objects as the merge takes place. To me that is the only reasonable approach...but this can be easily changed by removing those lines.

However, I am too concerned about potential side-effects to actually put this into production, which means we are stuck with an approach with tedious and painful object rearranging and decoupling-operations. Would be nice to have a comment from the official team. :-)

> Hibernate duplicate then child entity's on merge
> ------------------------------------------------
>
>                 Key: HHH-3332
>                 URL: http://opensource.atlassian.com/projects/hibernate/browse/HHH-3332
>             Project: Hibernate Core
>          Issue Type: Bug
>    Affects Versions: 3.2.6
>         Environment: JDK 1.6
> Oracle 9i
> Hibernate 3.2.6
> Hibernate Annotations 3.3.1
> Hibernate EntityManager 3.3.2
> Standalone Running
>            Reporter: Rodrigo de Assumpção
>            Assignee: Gail Badner
>            Priority: Critical
>         Attachments: ExampleBug.zip
>
>
> The method merge from EntityManager causes a duplication of child entity's.
> class Father:
>     @OneToMany(mappedBy = "father", cascade={CascadeType.ALL}, fetch=FetchType.LAZY) 
>     private List<Child> childList;
> class Child:
>     @ManyToOne  @JoinColumn(name = "ID_FATHER")
>     private Father father;
> class BugTest
>         EntityManagerFactory emf = Persistence.createEntityManagerFactory("JpaTestHB");
>         EntityManager em = emf.createEntityManager();
>         
>         Father f = (Father) em.createQuery("SELECT f FROM Father f  WHERE f.id = 1").getSingleResult();
>         
>         Child c = new Child();
>         c.setFather(f);
>         f.getChildList().add(c);
>         
>         em.getTransaction().begin();
>         em.merge(f);
>         em.getTransaction().commit();
> The execution of BugTest Class causes tow insert's on table "child".
> If you change the fetch mode to EAGER (into Father class) the problem not occurs.
> I make the same test with Toplink, and it make a unique insert, normal.

-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators: http://opensource.atlassian.com/projects/hibernate/secure/Administrators.jspa
-
For more information on JIRA, see: http://www.atlassian.com/software/jira

       



More information about the hibernate-issues mailing list