I have a root object (AccountNode) which has two Collections with cascade="all-delete-orphan" declarations (the two collections are "members" and "children").
The session code creates a new AccountNode, which creates a new UserNode and puts that into "children", and it creates a new NodeMember and puts that into "members". A NodeMember has a not-null="true" many-to-one mapping to the created UserNode. When I try to save AccountNode, it complains that NodeMember references a transient UserNode, even though that UserNode is a member of the "children" collection which has cascade="all". One possible solution is that I can put cascade="all" on the many-to-one mapping in the NodeMember to the user, and that gets around the error, but it changes the meaning of my objects - I actually do not want NodeMember associations to cascade, I want NodeMember to only reference UserNodes that are members of the same object graph by virtue of their parent-children cascading relationships. Also, the documentation discourages the use of cascade on many-to-one relationships.
Is my conjecture correct that when Hibernate saves a graph of cascading objects going from top to bottom, then upon verification of nullability constraints it doesn't check whether the referred-to object is part of the same graph (and, even though it may be transient at the time of the check, it will become persisted before the save operation concludes)?
The actual source code for this is posted at
http://www.geocities.com/yuricake/Test1.zip
Thanks!
Hibernate version:
3.0.5
Mapping documents:
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="test">
<class name="Node">
<id name="id" type="long" column="systemId">
<generator class="hilo"/>
</id>
<discriminator type="string" column="objectType"/>
<property name="name"/>
<many-to-one name="parent" column="parentId" cascade="all"/>
<set name="members" inverse="true" cascade="all-delete-orphan">
<key column="nodeId"/>
<one-to-many class="NodeMember"/>
</set>
</class>
</hibernate-mapping>
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="test">
<subclass name="ContainerNode" extends="Node">
<set name="children" inverse="true" cascade="all-delete-orphan">
<key column="parentId"/>
<one-to-many class="Node"/>
</set>
</subclass>
</hibernate-mapping>
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="test">
<subclass name="AccountNode" extends="ContainerNode"/>
</hibernate-mapping>
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="test">
<subclass name="UserNode" extends="Node">
<join table="User">
<key column="id"/>
<property name="userName"/>
<property name="displayName"/>
<property name="external"/>
</join>
</subclass>
</hibernate-mapping>
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="test">
<class name="NodeMember">
<id name="id" type="long" column="systemId">
<generator class="hilo"/>
</id>
<many-to-one name="node" column="nodeId" update="false" not-null="true"/>
<many-to-one name="user" column="userId" update="false" not-null="true"/>
</class>
</hibernate-mapping>
Code between sessionFactory.openSession() and session.close():Code:
Session s = factory.openSession();
Transaction tx = null;
try {
tx = s.beginTransaction();
AccountNode an = new AccountNode("GOLDMAN");
s.save(an);
tx.commit();
} catch (Exception e) {
if (tx != null) tx.rollback();
logger.error(StackTrace.getStack(e));
} finally {
s.close();
}
Full stack trace of any exception that occurs:Code:
ERROR - org.hibernate.PropertyValueException: not-null property references a null or transient value: test.NodeMember.user
at org.hibernate.engine.Nullability.checkNullability(Nullability.java:72)
at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:236)
at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:160)
at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:108)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:184)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:173)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:96)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:69)
at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:468)
at org.hibernate.engine.Cascades$5.cascade(Cascades.java:154)
at org.hibernate.engine.Cascades.cascadeAssociation(Cascades.java:771)
at org.hibernate.engine.Cascades.cascade(Cascades.java:720)
at org.hibernate.engine.Cascades.cascadeCollection(Cascades.java:895)
at org.hibernate.engine.Cascades.cascadeAssociation(Cascades.java:792)
at org.hibernate.engine.Cascades.cascade(Cascades.java:720)
at org.hibernate.engine.Cascades.cascade(Cascades.java:847)
at org.hibernate.event.def.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:363)
at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:265)
at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:160)
at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:108)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:184)
at org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:33)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:173)
at org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:27)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:69)
at org.hibernate.impl.SessionImpl.save(SessionImpl.java:481)
at org.hibernate.impl.SessionImpl.save(SessionImpl.java:476)
at Main.main(Main.java:40)
Name and version of the database you are using:MySQL 4.0.20a-debug-log
The generated SQL (show_sql=true):none
Debug level Hibernate log excerpt:Code:
DEBUG - opened session at timestamp: 4580386003050496
DEBUG - begin
DEBUG - opening JDBC connection
DEBUG - total checked-out connections: 0
DEBUG - using pooled JDBC connection, pool size: 0
DEBUG - current autocommit status: false
DEBUG - saving transient instance
DEBUG - opening JDBC connection
DEBUG - total checked-out connections: 1
DEBUG - opening new JDBC connection
DEBUG - created connection to: jdbc:mysql:///test, Isolation Level: 4
DEBUG - select next_hi from hibernate_unique_key for update
DEBUG - update hibernate_unique_key set next_hi = ? where next_hi = ?
DEBUG - closing JDBC connection (open PreparedStatements: 0, globally: 0) (open ResultSets: 0, globally: 0)
DEBUG - returning connection to pool, pool size: 1
DEBUG - new hi value: 0
DEBUG - generated identifier: 1, using strategy: org.hibernate.id.TableHiLoGenerator
DEBUG - saving [test.AccountNode#1]
DEBUG - processing cascade ACTION_SAVE_UPDATE for: test.AccountNode
DEBUG - done processing cascade ACTION_SAVE_UPDATE for: test.AccountNode
DEBUG - Wrapped collection in role: test.Node.members
DEBUG - Wrapped collection in role: test.ContainerNode.children
DEBUG - processing cascade ACTION_SAVE_UPDATE for: test.AccountNode
DEBUG - cascade ACTION_SAVE_UPDATE for collection: test.Node.members
DEBUG - cascading to saveOrUpdate: test.NodeMember
DEBUG - transient instance of: test.NodeMember
DEBUG - saving transient instance
DEBUG - opening JDBC connection
DEBUG - total checked-out connections: 1
DEBUG - using pooled JDBC connection, pool size: 0
DEBUG - select next_hi from hibernate_unique_key for update
DEBUG - update hibernate_unique_key set next_hi = ? where next_hi = ?
DEBUG - closing JDBC connection (open PreparedStatements: 0, globally: 0) (open ResultSets: 0, globally: 0)
DEBUG - returning connection to pool, pool size: 1
DEBUG - new hi value: 1
DEBUG - generated identifier: 32768, using strategy: org.hibernate.id.TableHiLoGenerator
DEBUG - saving [test.NodeMember#32768]
INFO - cleaning up connection pool: jdbc:mysql:///test
DEBUG - rollback
DEBUG - before transaction completion
DEBUG - before transaction completion
DEBUG - rolled back JDBC Connection
DEBUG - after transaction completion
DEBUG - after transaction completion
ERROR - org.hibernate.PropertyValueException: not-null property references a null or transient value: test.NodeMember.user
at org.hibernate.engine.Nullability.checkNullability(Nullability.java:72)
at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:236)
at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:160)
at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:108)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:184)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:173)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:96)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:69)
at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:468)
at org.hibernate.engine.Cascades$5.cascade(Cascades.java:154)
at org.hibernate.engine.Cascades.cascadeAssociation(Cascades.java:771)
at org.hibernate.engine.Cascades.cascade(Cascades.java:720)
at org.hibernate.engine.Cascades.cascadeCollection(Cascades.java:895)
at org.hibernate.engine.Cascades.cascadeAssociation(Cascades.java:792)
at org.hibernate.engine.Cascades.cascade(Cascades.java:720)
at org.hibernate.engine.Cascades.cascade(Cascades.java:847)
at org.hibernate.event.def.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:363)
at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:265)
at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:160)
at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:108)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:184)
at org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:33)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:173)
at org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:27)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:69)
at org.hibernate.impl.SessionImpl.save(SessionImpl.java:481)
at org.hibernate.impl.SessionImpl.save(SessionImpl.java:476)
at Main.main(Main.java:40)
DEBUG - closing session
DEBUG - closing JDBC connection [ (open PreparedStatements: 0, globally: 0) (open ResultSets: 0, globally: 0)]
DEBUG - closing JDBC connection
DEBUG - after transaction completion
DEBUG - after transaction completion