persistence@glassfish.java.net

Using EntityManager.merge to create new persistent entities?

From: Martin Bayly <mbayly_at_telus.net>
Date: Fri, 12 Jan 2007 10:00:48 -0800

[Sorry, this message became a lot more convoluted than I had intended -
perhaps I should just raise a JIRA issue instead and attach some sample
code?]

Is TopLink essentials designed to allow new persistent entities to be
created by calling EntityManager.merge rather than EntityManager.persist?

I was under the impression that this was supported by the JPA although
it doesn't seem to be explicitly stated in the JEE API javadocs.

In general this works with TopLink essentials but I ran into some
problems with it relating to child entities in a OneToMany mapping.

The problem seems to be that when you call the following for a new entity:

entityManager.merge(myEntity);

...TopLink caches a reference to myEntity in it's identity maps. If the
client subsequently changes myEntity this is also changing the entity
referenced by TopLink's identity maps. If the client calls
entityManager.merge(myEntity) again after opening a new persistence
context, it can cause problems with the TopLink change detection
policies as shown by the stack trace below. In my scenario, the problem
was caused by adding a new child entity to myEntity (related by a
OneToMany mapping). Because adding the child entity to myEntity in the
client also modified the instance in TopLink's identity map, it caused a
null pointer in the CollectionMapping comparison code because the child
entity being compared as the 'backup' wasn't the real backup - it had
been modified by changing the myEntity reference in the client code. As
such it contained a child entity without a persistent identifier which
causes the null pointer below.

I realize that in general clients are supposed to write code like:

myEntity = entityManager.merge(myEntity)

However, I don't think TopLink can assume that the object passed into
entityManager.merge won't subsequently be changed by the application.

This only seems to be a problem with scenarios like:

entityManager.merge(myEntity);
// close - re-open persistence context
entityManager.merge(myEntity);

The following doesn't exhibit the problem:

entityManager.persist(myEntity);
// close - re-open persistence context
entityManager.merge(myEntity);
// close - re-open persistence context
entityManager.merge(myEntity);

Exception in thread "main" java.lang.NullPointerException
    at
oracle.toplink.essentials.internal.identitymaps.CacheKey.equals(CacheKey.java:234)
    at
oracle.toplink.essentials.internal.sessions.ObjectChangeSet.equals(ObjectChangeSet.java:191)
    at
oracle.toplink.essentials.internal.sessions.ObjectChangeSet.equals(ObjectChangeSet.java:174)
    at
oracle.toplink.essentials.internal.helper.IdentityHashtable.contains(IdentityHashtable.java:167)
    at
oracle.toplink.essentials.internal.sessions.CollectionChangeRecord.addRemoveChange(CollectionChangeRecord.java:193)
    at
oracle.toplink.essentials.internal.queryframework.ContainerPolicy.compareCollectionsForChange(ContainerPolicy.java:287)
    at
oracle.toplink.essentials.mappings.CollectionMapping.compareCollectionsForChange(CollectionMapping.java:300)
    at
oracle.toplink.essentials.mappings.CollectionMapping.compareForChange(CollectionMapping.java:341)
    at
oracle.toplink.essentials.descriptors.changetracking.DeferredChangeDetectionPolicy.createObjectChangeSetThroughComparison(DeferredChangeDetectionPolicy.java:124)
    at
oracle.toplink.essentials.descriptors.changetracking.DeferredChangeDetectionPolicy.createObjectChangeSet(DeferredChangeDetectionPolicy.java:103)
    at
oracle.toplink.essentials.descriptors.changetracking.DeferredChangeDetectionPolicy.calculateChanges(DeferredChangeDetectionPolicy.java:81)
    at
oracle.toplink.essentials.internal.sessions.UnitOfWorkImpl.calculateChanges(UnitOfWorkImpl.java:476)
    at
oracle.toplink.essentials.internal.ejb.cmp3.base.RepeatableWriteUnitOfWork.writeChanges(RepeatableWriteUnitOfWork.java:228)
    at
oracle.toplink.essentials.internal.ejb.cmp3.base.EntityManagerImpl.flush(EntityManagerImpl.java:335)
    at
com.toplink.test.TopLinkTester.updateMailMessage(TopLinkTester.java:103)
    at com.toplink.test.TopLinkTester.main(TopLinkTester.java:22)