I've implemented cascades for a parent/child relationship in application code using the Lifecycle callbacks. When my application code calls SessionImpl.saveOrUpdate on my parent (Address) object, my code instantiates a new Distance object with a reference to the parent (Address) object and calls saveOrUpdate. No problem.
However, I migrated to the Interceptor framework, and the same relationships / mappings don't work. I get the "not-null property references a null or transient value" exception you see below.
I dug through the source code. And it turns out that SessionImpl first calls the Lifecycle.onSave method of the object being saved (line 811), then adds an Entry object to the session to flag the object with a status of SAVING (line 838), then calls the Interceptor.onSave method (line 855).
Here's the rub: When you save an child object with not-null references to a parent, hibernate calls SessionImpl.isUnsaved on the parent. SessionImpl.isUnsaved returns true if it finds an Entry object with a status of SAVING for the parent.
So if you try to save the child in a Lifecycle callback, no problem. But if you save the child in an Interceptor callback, you get a not-null / transient value constraint error.
This seems like the wrong behavior to me. And the hibernate docs describe the Interceptor framework as a "less-intrusive" alternative to Lifecycle callbacks. So I assume that the Interceptors are supposed to work just like the Lifecycle callbacks.
My questions are:
1) Is this the intended behavior?
2) If not, is it fixed anywhere?
3) What's the recommeded work-around. I've just relaxed the not-null constraint on the parent column and things seem to work fine. But this doesn't seem ideal to me.
Hibernate version:
2.1.1
Mapping documents:
PARENT OBJECT:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
<class name="com.jkassis.percolate.contact.address.Address" dynamic-update="false" dynamic-insert="false">
<id name="id" column="id" type="java.lang.Long" unsaved-value="null">
<generator class="increment">
</generator>
</id>
<property name="addressLine" type="string" update="true" insert="true" access="property" column="addressLine" not-null="false" unique="false"/>
<property name="countryRegion" type="java.lang.String" update="true" insert="true" access="property" column="countryRegion" length="100"/>
<property name="crossStreet" type="string" update="true" insert="true" access="property" column="crossStreet" not-null="false" unique="false"/>
<property name="latitude" type="double" update="true" insert="true" access="property" column="latitude" not-null="false" unique="false"/>
<property name="longitude" type="double" update="true" insert="true" access="property" column="longitude" not-null="false" unique="false"/>
<property name="neighborhood" type="string" update="true" insert="true" access="property" column="neighborhood" not-null="false" unique="false"/>
<property name="onlineMapUrl" type="string" update="true" insert="true" access="property" column="onlineMapUrl" not-null="false" unique="false"/>
<property name="postalCode" type="string" update="true" insert="true" access="property" column="postalCode" not-null="false" unique="false"/>
<property name="primaryCity" type="string" update="true" insert="true" access="property" column="primaryCity" not-null="false" unique="false"/>
<property name="secondaryCity" type="string" update="true" insert="true" access="property" column="secondaryCity" not-null="false" unique="false"/>
<property name="subRegion" type="string" update="true" insert="true" access="property" column="subRegion" not-null="false" unique="false"/>
<set name="distances" lazy="false" inverse="true" cascade="none" sort="unsorted">
<key column="addressA">
</key>
<one-to-many class="com.jkassis.percolate.contact.address.distance.Distance"/>
</set>
<!--
To add non XDoclet property mappings, create a file named
hibernate-properties-Address.xml
containing the additional properties and place it in your merge dir.
-->
</class>
</hibernate-mapping>
CHILD OBJECT:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
<class name="com.jkassis.percolate.contact.address.distance.Distance" dynamic-update="false" dynamic-insert="false">
<id name="id" column="id" type="java.lang.Long" unsaved-value="null">
<generator class="increment">
</generator>
</id>
<many-to-one name="addressA" class="com.jkassis.percolate.contact.address.Address" cascade="none" outer-join="auto" update="true" insert="true" access="property" column="addressA" not-null="true" unique="false"/>
<many-to-one name="addressB" class="com.jkassis.percolate.contact.address.Address" cascade="none" outer-join="auto" update="true" insert="true" access="property" column="addressB" not-null="true" unique="false"/>
<property name="distance" type="double" update="true" insert="true" access="property" column="distance" not-null="true" unique="false"/>
<property name="units" type="char" update="true" insert="true" access="property" column="units" not-null="true" unique="false"/>
<!--
To add non XDoclet property mappings, create a file named
hibernate-properties-Distance.xml
containing the additional properties and place it in your merge dir.
-->
</class>
</hibernate-mapping>
Code between sessionFactory.openSession() and session.close():
Too much to list.
Full stack trace of any exception that occurs:
[java] net.sf.hibernate.PropertyValueException: not-null property references a null or transient value: com.jkassis.percolate.contact.address.distance.Distance.addressA
[java] 0: net.sf.hibernate.impl.SessionImpl.checkNullability(SessionImpl.java:1211)
[java] 1: net.sf.hibernate.impl.SessionImpl.doSave(SessionImpl.java:873)
[java] 2: net.sf.hibernate.impl.SessionImpl.doSave(SessionImpl.java:817)
[java] 3: net.sf.hibernate.impl.SessionImpl.saveWithGeneratedIdentifier(SessionImpl.java:740)
[java] 4: net.sf.hibernate.impl.SessionImpl.save(SessionImpl.java:717)
[java] 5: net.sf.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:1322)
[java] 6: org.springframework.orm.hibernate.HibernateTemplate$10.doInHibernate(HibernateTemplate.java:259)
[java] 7: org.springframework.orm.hibernate.HibernateTemplate.execute(HibernateTemplate.java:150)
[java] 8: org.springframework.orm.hibernate.HibernateTemplate.saveOrUpdate(HibernateTemplate.java:257)
[java] 9: com.jkassis.gutz.persistence.hibernate.DAOHibImpl.saveOrUpdate(DAOHibImpl.java:246)
[java] 10: com.jkassis.percolate.contact.address.distance.DistanceDAOHibImpl.save(DistanceDAOHibImpl.java:222)
[java] 11: com.jkassis.percolate.contact.address.distance.DistanceDAOHibImpl.access$2(DistanceDAOHibImpl.java:221)
[java] 12: com.jkassis.percolate.contact.address.distance.DistanceDAOHibImpl$AddressDataAccessListener.beforeSave(DistanceDAOHibImpl.java:104)
[java] 13: com.jkassis.gutz.persistence.hibernate.DataAccessInterceptor.fireDataAccessEvent(DataAccessInterceptor.java:231)
[java] 14: com.jkassis.gutz.persistence.hibernate.DataAccessInterceptor.onSave(DataAccessInterceptor.java:178)
[java] 15: net.sf.hibernate.impl.SessionImpl.doSave(SessionImpl.java:855)
[java] 16: net.sf.hibernate.impl.SessionImpl.doSave(SessionImpl.java:817)
[java] 17: net.sf.hibernate.impl.SessionImpl.saveWithGeneratedIdentifier(SessionImpl.java:740)
[java] 18: net.sf.hibernate.impl.SessionImpl.save(SessionImpl.java:717)
[java] 19: net.sf.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:1322)
[java] 20: org.springframework.orm.hibernate.HibernateTemplate$10.doInHibernate(HibernateTemplate.java:259)
[java] 21: org.springframework.orm.hibernate.HibernateTemplate.execute(HibernateTemplate.java:150)
[java] 22: org.springframework.orm.hibernate.HibernateTemplate.saveOrUpdate(HibernateTemplate.java:257)
[java] 23: com.jkassis.gutz.persistence.hibernate.DAOHibImpl.saveOrUpdate(DAOHibImpl.java:246)
[java] 24: com.jkassis.percolate.contact.address.AddressDAOHibImpl.save(AddressDAOHibImpl.java:148)
[java] 25: sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[java] 26: sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
[java] 27: sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
[java] 28: java.lang.reflect.Method.invoke(Method.java:582)
[java] 29: org.springframework.aop.framework.AopProxyUtils.invokeJoinpointUsingReflection(AopProxyUtils.java:59)
[java] 30: org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:149)
[java] 31: org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:118)
[java] 32: org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:169)
[java] 33: org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:138)
[java] 34: org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:148)
[java] 35: $Proxy0.save(Unknown Source)
[java] 36: sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[java] 37: sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
[java] 38: sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
[java] 39: java.lang.reflect.Method.invoke(Method.java:582)
[java] 40: com.jkassis.gutz.test.TestObjBaseManager.invoke(TestObjBaseManager.java:275)
[java] 41: org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:138)
[java] 42: org.springframework.aop.framework.Cglib2AopProxy.intercept(Cglib2AopProxy.java:144)
[java] 43: com.jkassis.percolate.contact.address.AddressDAOTestObjBase$$EnhancerByCGLIB$$4429471a.getTestAddressPersistent(<generated>)
[java] 44: com.jkassis.percolate.contact.address.neighborhood.NeighborhoodDAOTestObjBase.getTestNeighborhoodPersistent(NeighborhoodDAOTestObjBase.java:46)
[java] 45: com.jkassis.percolate.contact.address.neighborhood.NeighborhoodDAOTestObjBase$$FastClassByCGLIB$$ac6bd99a.invoke(<generated>)
[java] 46: net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:183)
[java] 47: org.springframework.aop.framework.Cglib2AopProxy$MethodInvocationImpl.invokeJoinpoint(Cglib2AopProxy.java:393)
[java] 48: org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:118)
[java] 49: com.jkassis.gutz.test.TestObjBaseManager.invoke(TestObjBaseManager.java:262)
[java] 50: org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:138)
[java] 51: org.springframework.aop.framework.Cglib2AopProxy.intercept(Cglib2AopProxy.java:144)
[java] 52: com.jkassis.percolate.contact.address.neighborhood.NeighborhoodDAOTestObjBase$$EnhancerByCGLIB$$513f3d70.getTestNeighborhoodPersistent(<generated>)
[java] 53: sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[java] 54: sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
[java] 55: sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
[java] 56: java.lang.reflect.Method.invoke(Method.java:582)
[java] 57: com.jkassis.gutz.test.TestObjBaseManager.loadAll(TestObjBaseManager.java:343)
[java] 58: com.jkassis.gutz.test.TestObjBaseManager.main(TestObjBaseManager.java:508)
[java] 59: sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[java] 60: sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
[java] 61: sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
[java] 62: java.lang.reflect.Method.invoke(Method.java:582)
[java] 63: org.apache.tools.ant.taskdefs.ExecuteJava.run(ExecuteJava.java:196)
[java] 64: org.apache.tools.ant.taskdefs.ExecuteJava.execute(ExecuteJava.java:133)
[java] 65: org.apache.tools.ant.taskdefs.Java.run(Java.java:661)
[java] 66: org.apache.tools.ant.taskdefs.Java.executeJava(Java.java:168)
[java] 67: org.apache.tools.ant.taskdefs.Java.execute(Java.java:77)
[java] 68: org.apache.tools.ant.UnknownElement.execute(UnknownElement.java:269)
[java] 69: org.apache.tools.ant.Task.perform(Task.java:364)
[java] 70: org.apache.tools.ant.Target.execute(Target.java:301)
[java] 71: org.apache.tools.ant.Target.performTasks(Target.java:328)
[java] 72: org.apache.tools.ant.Project.executeTarget(Project.java:1215)
[java] 73: org.apache.tools.ant.Project.executeTargets(Project.java:1063)
[java] 74: org.eclipse.ant.internal.ui.antsupport.InternalAntRunner.run(InternalAntRunner.java:377)
[java] 75: org.eclipse.ant.internal.ui.antsupport.InternalAntRunner.main(InternalAntRunner.java:135)
Name and version of the database you are using:
MySql 4.x
The generated SQL (show_sql=true):
Not required.
Debug level Hibernate log excerpt:
Not required.
|