[
http://opensource.atlassian.com/projects/hibernate/browse/HHH-2326?page=c...
]
Avram Cherry commented on HHH-2326:
-----------------------------------
I'm posting this here instead of the issue that it 'duplicates' (it
doesn't, entirely) because it has more watchers.
I found a way around this bug since I too am stuck with a legacy database.
I've only tested this to work with the 'persist' event. It will probably need
refinement to work with 'merge'. In this example, I will assume that you're
doing 'persist'.
First, remove the persist cascade indicator from any class that uses it on the
relationship to the class with the compound key.
Next, make the following interface:
---
public interface CascadePersistLast {
public void collectEntitiesToPersistLast(Set s);
}
---
Next, make the following class:
---
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import org.hibernate.HibernateException;
import org.hibernate.event.EventSource;
import org.hibernate.event.FlushEvent;
import org.hibernate.event.FlushEventListener;
import org.hibernate.event.PersistEvent;
import org.hibernate.event.PersistEventListener;
public class ExplicitCascadeOrderEventListener implements PersistEventListener,
FlushEventListener {
boolean isAlreadyFlushing = false;
@SuppressWarnings("unchecked")
static protected Map<EventSource, Set> entitiesToPersistLast = new
WeakHashMap<EventSource, Set>();
private static final long serialVersionUID = 1L;
@SuppressWarnings("unchecked")
@Override
public void onPersist(PersistEvent event) throws HibernateException {
doOnPersist(event, new HashMap());
}
@SuppressWarnings("unchecked")
@Override
public void onPersist(PersistEvent event, Map createdAlready)
throws HibernateException {
doOnPersist(event, createdAlready);
}
@SuppressWarnings("unchecked")
private void doOnPersist(PersistEvent event, Map createdAlready) throws
HibernateException {
if (event.getObject() instanceof CascadePersistLast) {
CascadePersistLast p = (CascadePersistLast) event.getObject();
if (! entitiesToPersistLast.containsKey(event.getSession())) {
entitiesToPersistLast.put(event.getSession(), new HashSet());
}
p.collectEntitiesToPersistLast(entitiesToPersistLast.get(event.getSession()));
}
}
@SuppressWarnings("unchecked")
@Override
public void onFlush(FlushEvent event) throws HibernateException {
if (entitiesToPersistLast.containsKey(event.getSession())) {
EventSource session = event.getSession();
Set entities = entitiesToPersistLast.get(session);
entitiesToPersistLast.remove(session);
if (isAlreadyFlushing) {
throw new HibernateException("Assertion failed!");
}
isAlreadyFlushing = true;
for (Object e : entities) {
if (! session.contains(e)) {
session.persist(e);
}
}
session.flush();
isAlreadyFlushing = false;
}
}
}
---
Now implement CascadePersistLast on any entity class that you want to cascade persist on,
and add any instances of the entity with the compound key to this set when
collectEntitiesToPersistLast is called. It should be safe to pass already-persistant
instances, since it won't try to persist it twice.
Finally, alter your hibernate.hbm.xml to include the following, or programatically achieve
the equivalent:
<event type="create">
<listener
class="org.hibernate.event.def.DefaultPersistEventListener"/>
<listener class="yourpackage.ExplicitCascadeOrderEventListener"/>
</event>
<event type="flush">
<listener
class="org.hibernate.event.def.DefaultFlushEventListener"/>
<listener class="yourpackage.ExplicitCascadeOrderEventListener"/>
</event>
Note that the order of the listeners within the vent is VERY important. The custom
listener, at least for flush, MUST be the very last one.
When I was working with the HibernateTest.zip contents when doing a merge, I noticed that
Hibernate does not seem to allow you to merge an entity that contains associations that
are neither cascading or already persistent, so in order for my approach to work with
that, you might need to take the further step of actually temporarily removing the
transient instances before the first flush, and restore them before the 2nd. Since I
don't use merge in conjunction with this particular problem, I didn't investigate
further.
Also, that static map isn't thread-safe. I guess you should synchronize it or maybe
replace it with a threadlocal.
You may use this code however you'd wish, but it may, of course, cause errors, lose
data or feed chocolate to your dog.
NullPointerException from merge on composite id
-----------------------------------------------
Key: HHH-2326
URL:
http://opensource.atlassian.com/projects/hibernate/browse/HHH-2326
Project: Hibernate Core
Issue Type: Bug
Components: core
Affects Versions: 3.2.1
Environment: Hibernate 3.2.1ga, tested against Oracle 10g and Derby
Reporter: Greg Adams
Assignee: Diego Plentz
Priority: Critical
Attachments: HibernateMergeBug.zip
Merge is throwing an NPE from the bowels of Hibernate when I have a class mapped with a
composite ID.
Stacktrace:
[junit] java.lang.NullPointerException
[junit] at org.hibernate.type.AbstractType.getHashCode(AbstractType.java:112)
[junit] at org.hibernate.type.AbstractType.getHashCode(AbstractType.java:120)
[junit] at org.hibernate.type.EntityType.getHashCode(EntityType.java:279)
[junit] at org.hibernate.type.ComponentType.getHashCode(ComponentType.java:189)
[junit] at org.hibernate.engine.EntityKey.generateHashCode(EntityKey.java:104)
[junit] at org.hibernate.engine.EntityKey.<init>(EntityKey.java:48)
[junit] at
org.hibernate.event.def.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:100)
[junit] at org.hibernate.impl.SessionImpl.fireMerge(SessionImpl.java:687)
[junit] at org.hibernate.impl.SessionImpl.merge(SessionImpl.java:669)
[junit] at org.hibernate.engine.CascadingAction$6.cascade(CascadingAction.java:245)
[junit] at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:268)
[junit] at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:216)
[junit] at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:169)
[junit] at org.hibernate.engine.Cascade.cascadeCollectionElements(Cascade.java:296)
[junit] at org.hibernate.engine.Cascade.cascadeCollection(Cascade.java:242)
[junit] at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:219)
[junit] at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:169)
[junit] at org.hibernate.engine.Cascade.cascade(Cascade.java:130)
[junit] at
org.hibernate.event.def.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:437)
[junit] at
org.hibernate.event.def.DefaultMergeEventListener.entityIsTransient(DefaultMergeEventListener.java:194)
[junit] at
org.hibernate.event.def.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:123)
[junit] at
org.hibernate.event.def.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:53)
[junit] at org.hibernate.impl.SessionImpl.fireMerge(SessionImpl.java:677)
[junit] at org.hibernate.impl.SessionImpl.merge(SessionImpl.java:661)
[junit] at org.hibernate.impl.SessionImpl.merge(SessionImpl.java:665)
[junit] at com.foo.test.HibernateTest.doMerge(Unknown Source)
[junit] at com.foo.test.HibernateTest.testInsert(Unknown Source)
[junit] at
org.junit.internal.runners.TestMethodRunner.executeMethodBody(TestMethodRunner.java:99)
[junit] at
org.junit.internal.runners.TestMethodRunner.runUnprotected(TestMethodRunner.java:81)
[junit] at
org.junit.internal.runners.BeforeAndAfterRunner.runProtected(BeforeAndAfterRunner.java:34)
[junit] at
org.junit.internal.runners.TestMethodRunner.runMethod(TestMethodRunner.java:75)
[junit] at
org.junit.internal.runners.TestMethodRunner.run(TestMethodRunner.java:45)
[junit] at
org.junit.internal.runners.TestClassMethodsRunner.invokeTestMethod(TestClassMethodsRunner.java:71)
[junit] at
org.junit.internal.runners.TestClassMethodsRunner.run(TestClassMethodsRunner.java:35)
[junit] at
org.junit.internal.runners.TestClassRunner$1.runUnprotected(TestClassRunner.java:42)
[junit] at
org.junit.internal.runners.BeforeAndAfterRunner.runProtected(BeforeAndAfterRunner.java:34)
[junit] at org.junit.internal.runners.TestClassRunner.run(TestClassRunner.java:52)
[junit] at junit.framework.JUnit4TestAdapter.run(JUnit4TestAdapter.java:32)
I've attached a zipped-up project that reproduces the error. Just extract and run
ant.
--
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....
-
For more information on JIRA, see:
http://www.atlassian.com/software/jira