-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 6 posts ] 
Author Message
 Post subject: Persisting Graphs
PostPosted: Sun Apr 10, 2005 3:00 am 
Newbie

Joined: Sun Apr 10, 2005 2:20 am
Posts: 15
For the past week or so I have been using Hibernate 3.0 and MySQL 4.0.18 to persist a graph of objects. The graph is sumarized as follows:

Quote:
The graph is composed of mainly three objects: Parent, Node and Link. Parent contains a Set of Nodes and a Set of Links. A Link contains a reference to a source Node and another one to a target Node. The Node has a Set of incomming Links and another one of outgoing Links. Both Node and Link have a reference back to Parent.


Code goes along these lines:
Quote:
Parent p = new Parent();

Node src = new Node();
src.setParent(p);
p.getNodes().add(src);

Node tgt = new Node();
tgt.setParent(p);
p.getNodes().add(tgt);

Link lnk = new Link();
p.getLinks().add(lnk);
lnk.setSourceNode(src);
lnk.setTargetNode(tgt);

src.getOutgoings().add(lnk);
tgt.getIncomings().add(lnk);

session.save(p);

Here is a section of the Parent.hbm.xml file:
Quote:
<set name="links" inverse="true" cascade="all-delete-orphan">
<key column="parent"/>
<one-to-many class="Link"/>
</set>

<set name="nodes" inverse="true" cascade="all-delete-orphan">
<key column="parent"/>
<one-to-many class="Node"/>
</set>


Here is a section of the Node.hbm.xml file:
Quote:

<many-to-one name="parent" not-null="true"/>

<many-to-one name="sourceNode" not-null="true"/>
<many-to-one name="targetNode" not-null="true"/>

Here is a section of the Link.hbm.xml file:
Quote:

<many-to-one name="parent" not-null="true"/>

<set name="incoming" inverse="true" cascade="all-delete-orphan">
<key column="sourceNode"/>
<one-to-many class="Link"/>
</set>

<set name="outgoing" inverse="true" cascade="all-delete-orphan">
<key column="targetNode"/>
<one-to-many class="Link"/>
</set>




When I try to persist this graph using session.save(aParent) I get the following exception:

Quote:
org.hibernate.PropertyValueException: not-null property references a null or transient value:
Link.targetNode
at org.hibernate.engine.Nullability.checkNullability(Nullability.java:72)
at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:234)
at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:158)
at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:107)
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:416)
at org.hibernate.engine.Cascades$5.cascade(Cascades.java:153)
at org.hibernate.engine.Cascades.cascade(Cascades.java:721)
at org.hibernate.engine.Cascades.cascadeCollection(Cascades.java:860)
at org.hibernate.engine.Cascades.cascade(Cascades.java:739)
at org.hibernate.engine.Cascades.cascade(Cascades.java:817)
at org.hibernate.event.def.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:361)
at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:263)
at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:158)
at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:107)
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:416)
at org.hibernate.engine.Cascades$5.cascade(Cascades.java:153)
at org.hibernate.engine.Cascades.cascade(Cascades.java:721)
at org.hibernate.engine.Cascades.cascadeCollection(Cascades.java:860)
at org.hibernate.engine.Cascades.cascade(Cascades.java:739)
at org.hibernate.engine.Cascades.cascade(Cascades.java:817)
at org.hibernate.event.def.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:361)
at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:263)
at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:158)
at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:107)
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:429)
at org.hibernate.impl.SessionImpl.save(SessionImpl.java:424)
at Main.createGraph(Main.java:452)
at Main.main(Main.java:383)


After some Hibernate debugging it seems that when Hibernate tries to save the only Link object I have in my test graph it nullifies either the source or target Node (it seems to vary for each debugging session). Since both Link.sourceNode and Link.targetNode cannot be null, the exception is thown.

Some questions:
(1) Since I would expect an ORM implementation to take care of graphs, what am I doing wrong here?
(2) Shouldnt Hibernate first reach all dependent object and then start saving them?
(3) If this is a bug, is there a work around?

Any help would be greatly appreciated!

thanks,

-- yuri

Read the rules before posting!
http://www.hibernate.org/ForumMailingli ... AskForHelp

Hibernate version:

Mapping documents:

Code between sessionFactory.openSession() and session.close():

Full stack trace of any exception that occurs:

Name and version of the database you are using:

The generated SQL (show_sql=true):

Debug level Hibernate log excerpt:


Top
 Profile  
 
 Post subject:
PostPosted: Sun Apr 10, 2005 11:18 am 
Regular
Regular

Joined: Thu Oct 07, 2004 4:45 pm
Posts: 92
This is almost certainly a cascade problem. I find that when you persist an object graph with cycles in it, you need the save-update cascades going in the "opposite" directions (e.g. on many-to-one elements) back up to the root entity you told it to save. It seems to depend on how Hibernate walks the object graph during cascades. You can turn up the log level on the Cascades class to see this.

In my view, this is one of the few frustrating things about working with Hibernate and an area that could stand some improvement. As a developer, I shouldn't have to worry so much about how it will walk the object graph. For example, given a graph like this:
Code:
  A
/ \
B-C

If the relations from A->B and A->C declare cascade="all", and I try to save A, it ought to work. But it doesn't unless you also have cascade="all" on the inverse relations.


Top
 Profile  
 
 Post subject:
PostPosted: Sun Apr 10, 2005 11:39 am 
Newbie

Joined: Sun Apr 10, 2005 2:20 am
Posts: 15
Yes, that confirms what I saw debugging Hibernate. The cascading follows the property values in order and depth first. In your grapth Hibernate saves A, B and then reaches C, which is not yet saved and is assumed to be a transient object, which causes its nullification.

I added cascade="all" to the many-to-one relations on Link and it worked fine.

I dont yet fully understand the implications of the cascade attribute, but I do agree in principle that having or not having a cycle should not change the behavior of Hibernate.

Thanks for the help!

-- yuri


Top
 Profile  
 
 Post subject:
PostPosted: Sun Apr 10, 2005 1:42 pm 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 12:50 pm
Posts: 5130
Location: Melbourne, Australia
rhasselbaum wrote:
This is almost certainly a cascade problem. I find that when you persist an object graph with cycles in it, you need the save-update cascades going in the "opposite" directions (e.g. on many-to-one elements) back up to the root entity you told it to save. It seems to depend on how Hibernate walks the object graph during cascades. You can turn up the log level on the Cascades class to see this.

In my view, this is one of the few frustrating things about working with Hibernate and an area that could stand some improvement. As a developer, I shouldn't have to worry so much about how it will walk the object graph. For example, given a graph like this:
Code:
  A
/ \
B-C

If the relations from A->B and A->C declare cascade="all", and I try to save A, it ought to work. But it doesn't unless you also have cascade="all" on the inverse relations.



This not even remotely true. The overwhelmingly common case is that bidirectional cascade does not make semantic sense, from the point of view of the lifecycle of the data.

Should an Item become persistent when we make a new Bid persistent? Should a Group become persistent when we make a new User persistent? Of course not. From a business point of view, relationships have defined lifecycles, and cascade create happens in one direction only.

Even if, in the uncommon case, bidirectional cascade does make sense, it is almost certainly not true that the actual cascade setting would be the same on each side. (One side might be "all", whereas the other would be "save-update, persist, lock", for example.

And how in hell could this be "frustrating", when as you have correctly observed, enabling this almost-always-nonsensical bidirectional cascade is as simple as setting an attribute on the other side of the relationship??


Top
 Profile  
 
 Post subject:
PostPosted: Sun Apr 10, 2005 3:28 pm 
Regular
Regular

Joined: Thu Oct 07, 2004 4:45 pm
Posts: 92
gavin wrote:
This not even remotely true. The overwhelmingly common case is that bidirectional cascade does not make semantic sense, from the point of view of the lifecycle of the data.


I agree with you! I don't want to put a save-update cascade rule on the inverse relations. But this is the only way I've been able to do "one-call" saves of object graphs containing cycles, like the example I mentioned in my reply. If there is a better way to accomplish this, I'm all ears.

gavin wrote:
And how in hell could this be "frustrating", when as you have correctly observed, enabling this almost-always-nonsensical bidirectional cascade is as simple as setting an attribute on the other side of the relationship??


It's frustrating to debug. It makes it more difficult to save (or delete) complex graphs of objects using cascades because you have to figure out where there is a cycle that's causing a problem.

There's no reason to become defensive. Hibernate is a great tool. In my company, I'm its biggest fan. But it's not perfect. It seems like any time someone on this forum suggests otherwise, the Hibernate team takes it as a personal affront.


Top
 Profile  
 
 Post subject:
PostPosted: Sun Apr 10, 2005 7:47 pm 
Newbie

Joined: Sun Apr 10, 2005 2:20 am
Posts: 15
gavin wrote:
Even if, in the uncommon case, bidirectional cascade does make sense, it is almost certainly not true that the actual cascade setting would be the same on each side. (One side might be "all", whereas the other would be "save-update, persist, lock", for example.


Gavin,
I actually have one side of my bidirectional cascade set to cascade="all" and the other side set to cascade="all-delete-orphan". Following from your paragraph above this is wrong, no? What is the implication of setting both sides to "all"?


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 6 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.