-->
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.  [ 27 posts ]  Go to page 1, 2  Next
Author Message
 Post subject: Moving (reparenting) nodes in a tree
PostPosted: Sat Jul 08, 2006 12:02 pm 
Newbie

Joined: Sat Jul 08, 2006 10:33 am
Posts: 15
Hello,

I'm using Hibernate 3.1.3 to create a file explorer-like structure (ie. a tree of nodes of the same class).

That works perfectly (adding nodes, deleting nodes deletes child nodes but not parents, I can reordering the children of a node).

Now I tried to move a node in the tree (ie. reparanting a node). Java code to move a node:

Code:
node.setParent(newParent);

setParent() looks like this:

Code:
if (this.parent != null) this.parent.getChildren().remove(this);
this.parent = parent;
if (this.parent != null) this.parent.getChildren().add(this);

Now, I get an ObjectDeletedException as soon as I call session.flush() because of the "remove(this)" which makes Hibernate think the object should be deleted. How can I solve this?

The mapping (just in case):

Code:
<many-to-one name="parent" column="PARENT" class="Node" cascade="none" />
<list name="children" cascade="all-delete-orphan">
    <key column="PARENT" />
    <index column="POS" />
    <one-to-many class="Node" />
</list>

_________________
Aaron "Optimizer" Digulla
http://www.philmann-dark.de/


Top
 Profile  
 
 Post subject:
PostPosted: Sun Jul 09, 2006 11:47 pm 
Expert
Expert

Joined: Thu Dec 23, 2004 9:08 pm
Posts: 2008
Are you doing this outside of a transaction? I think that it should work in a transaction.

If it is in a transaction, then hack together this test case: make a fake "setParent" that does this:
Code:
if (parent != null)
{
  this.parent.getChildren().add(this);
  session.flush();
}

if (this.parent != null) this.parent.getChildren().remove(this);
this.parent = parent;
That will prevent "this" from ever being an orphan, which it is for a brief period during your setParent(). If that fixes it, then I'd consider this an hibernate bug, or at least a feature that should be removed ASAP. Until it gets fixed, you'll have to move the reparenting function into business logic so that you can save the child with two parents before removing it from the old parent.

_________________
Code tags are your friend. Know them and use them.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Jul 10, 2006 7:50 am 
Newbie

Joined: Sat Jul 08, 2006 10:33 am
Posts: 15
I'm doing this inside of a transaction.

While trying to implement your solution, I ran into a strange behavior: I have to implement your setParent() in the base class (I'm using the abstract/public pattern where an abstract base class implements the POJO stuff and the public class implements the business logic).

As soon as I started to replace this.parent with getParent(), I got very strange exceptions from CGLIB.

When I implemented the code in the abstract base class, I got the same error as before:

Code:
org.hibernate.ObjectDeletedException: deleted object would be re-saved by cascade (remove deleted object from associations): [de.philmanndark.sensei.model.Knowledge#26]
   at org.hibernate.impl.SessionImpl.forceFlush(SessionImpl.java:999)
   at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:168)
   at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:98)
   at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:70)
...

Also, I cannot use this code to implement all the other test cases (like adding a child to the parent node) because as soon as I call getChildren().add(), parent must be non-null, or Hibernate will complain.

So I used this code:

Code:
    public void setParent2 (Knowledge newParent)
    {
   Knowledge oldParent = this.parent;
   
   this.parent = newParent;
   if (newParent != null) {
       newParent.getChildren().add((Knowledge)this);
       session.flush();
        }

   if (oldParent != null)
       oldParent.getChildren().remove(this);
    }

Unfortunately, when I call flush() Hibernates complains that a foreign key constraint is violated (which can't be true because it could load the object).

I guess, I should create a test case based on HSQLDB so someone who knows Hibernate can have a look and debug this. Can I post this code here or is there a better place?

_________________
Aaron "Optimizer" Digulla
http://www.philmann-dark.de/


Top
 Profile  
 
 Post subject:
PostPosted: Mon Jul 10, 2006 5:55 pm 
Expert
Expert

Joined: Thu Dec 23, 2004 9:08 pm
Posts: 2008
Well, from what you've said, there is a workaround solution: do the reparenting in two steps. That is, remove the child from parentA, save parentA and evict the child (and set its ID to unsaved-value), then add the child to parentB and save it. Obviously not ideal, but if you have to get it working before this issue is resolved, it'll do.

I suppose that this is a reasonable place to post your test case; alternatively, you could post it at the JIRA site. Head back to the hibernate home page, and follow the links to the JIRA bug tracking system. If you're confident that this is a bug (and I suspect that it is, or at least a yet-to-be-implemented feature), then it would be better on JIRA.

_________________
Code tags are your friend. Know them and use them.


Top
 Profile  
 
 Post subject:
PostPosted: Tue Jul 11, 2006 4:16 am 
Newbie

Joined: Sat Jul 08, 2006 10:33 am
Posts: 15
Guess what: Hibernate is smarter than you think.

When I try to setParent(null);flush();setParent(newParent);, I get:

Code:
Found two representations of same collection: org.hibernate.parentchild.Node.children

So I tried to delete the child and insert it again but Hibernate also catches this case. I guess, it recognizes the object as something which it has seen before.

I actually have to clone the child node which might work for me but is not an option for more complex objects.

I have now reduced this into a test case and will post that on JIRA.

Thanks a lot!

_________________
Aaron "Optimizer" Digulla
http://www.philmann-dark.de/


Top
 Profile  
 
 Post subject:
PostPosted: Tue Jul 11, 2006 6:59 am 
Hibernate Team
Hibernate Team

Joined: Mon Aug 25, 2003 9:11 pm
Posts: 4592
Location: Switzerland
Case closed, duplicate. Tenwit, please don't send people to JIRA without telling them to do a search first, these days 99% of all issues are bogus and waste a lot of developer time.


Top
 Profile  
 
 Post subject:
PostPosted: Tue Jul 11, 2006 7:00 am 
Hibernate Team
Hibernate Team

Joined: Mon Aug 25, 2003 9:11 pm
Posts: 4592
Location: Switzerland
And the phrase "confident that this is a bug" is also redundant, everything users don't understand is of course a bug in Hibernate ;)


Top
 Profile  
 
 Post subject:
PostPosted: Tue Jul 11, 2006 8:23 am 
Newbie

Joined: Sat Jul 08, 2006 10:33 am
Posts: 15
Ah yes ...

http://opensource.atlassian.com/project ... se/HHH-785: Unfortunately, this would be very, very difficult to implement.

I did search JIRA, btw, but the description of the bug would have never made me guess it was related.

Hm. So what do I do, now?

Do you say it is better to forget about Hibernate or is there some workaround?

_________________
Aaron "Optimizer" Digulla
http://www.philmann-dark.de/


Top
 Profile  
 
 Post subject:
PostPosted: Tue Jul 11, 2006 8:40 am 
Hibernate Team
Hibernate Team

Joined: Mon Aug 25, 2003 9:11 pm
Posts: 4592
Location: Switzerland
Just disable orphan deletion...


Top
 Profile  
 
 Post subject:
PostPosted: Tue Jul 11, 2006 10:44 am 
Newbie

Joined: Sat Jul 08, 2006 10:33 am
Posts: 15
....

Okaaaayyy ... this works. In retrospect, it somewhat makes sense but the documentation of the feature is a bit confusing.

I've read the online documentation and "Hibernate In Action".

Do I understand this correctly that I now have to make sure myself that orphans (child nodes which are no longer in any parents' list) must be managed by myself?

I saw in the log that removing it from the list just updates the position in the list to -1. The id of the parent is not set to null nor is a DELETE generated.

Since this seems a pretty confusing area, how about including my test case as an example?

_________________
Aaron "Optimizer" Digulla
http://www.philmann-dark.de/


Top
 Profile  
 
 Post subject:
PostPosted: Tue Jul 11, 2006 11:12 am 
Hibernate Team
Hibernate Team

Joined: Mon Aug 25, 2003 9:11 pm
Posts: 4592
Location: Switzerland
Orphan delete is an ugly hack that violates the concepts of Entity and Value Type instances in Hibernate. It enables Value Type life cycle semantics on an Entity association.

If you remove a reference to an Entity instance (Item, Order, Customer) from a collection you do _NOT WANT_ this Entity instance deleted in the database. Other references to the same instance may exist and you'd get a foreign key constraint violation.

If you remove a reference to a Value Typed instance (Date, String, MonetaryAmount, Addresss, Image) from a collection you _WANT_ this Value Type instance deleted in the database. Conceptually no other references to this instance can exist.

By enabling orphan deletion on a collection in Hibernate you give Hibernate the guarantee that any Entity instances referenced in this collection can be deleted if the (hopefully only existing) reference is removed from the collection. This is a stronger guarantee than any other and overrides any other state once the element from the collection is removed. But orphan deletion it can be convenient feature in some cases. If this _FEATURE_ doesn't work in some cases, disable it.

This is not confusing at all if you understand Java, pointers, pass by reference, and pass by value.

You can post any example on the Wiki, somebody will hopefully have the time to correct it if its wrong. Don't post non-working code on the Wiki though.


Top
 Profile  
 
 Post subject:
PostPosted: Tue Jul 11, 2006 11:31 am 
Hibernate Team
Hibernate Team

Joined: Mon Aug 25, 2003 9:11 pm
Posts: 4592
Location: Switzerland
Oh, and your mapping is completely broken anyway. Read up on what "inverse" does.


Top
 Profile  
 
 Post subject:
PostPosted: Tue Jul 11, 2006 3:01 pm 
Newbie

Joined: Sat Jul 08, 2006 10:33 am
Posts: 15
Quote:
your mapping is completely broken anyway

is not very polite. If your goal is to help newbies like me, it might be more appropriate to avoid such sentences. Just say "I just noticed the usage of inverse in your mapping is wrong. Please read the documentation about it."

Actually, I don't want my child nodes to be attached to several parent nodes at the same time (this is reflected in my mapping: there is only one parent per node), so the orphan rule matches my goals. From an OO perpective, they are immutable values.

Also, your online-documentation specifically asks to use all-delete-orphans.

Since I'm new to Hibernate, I didn't know it was an ugly hack and shouldn't be used. Also, there is nothing in the documentation which hints in this direction, so how can I know?

Let's come back to the comment about misuse of inverse. Let's see the docs:

Quote:
By specifying inverse="true", we explicitly tell Hibernate which end of the association it should synchronize with the database.

(Chapter 3.7.4 Making the association bidirectional from "Hibernate in Action")

That's what I want: The parent attribute is what really exists in the database. The children collection is just virtual. I want Hibernate to propagte changes over the parent attribute.

Interestingly enough, all my test cases which add, create, delete and move children around (thanks to your input, all work now) don't care if or where I specify inverse=true.

Since the book is already one year old, I had a look in the Online documentation from the website. Here is the example:

Code:
<set name="children" inverse="true">
    <key column="parent_id"/>
    <one-to-many class="Child"/>
</set>

So this means the documentation is wrong?

Or is this a special feature of list mappings? I couldn't find much about them in the docs besides that you should use them when the order of the items is important (which it is in my case).

_________________
Aaron "Optimizer" Digulla
http://www.philmann-dark.de/


Top
 Profile  
 
 Post subject:
PostPosted: Tue Jul 11, 2006 4:25 pm 
Hibernate Team
Hibernate Team

Joined: Mon Aug 25, 2003 9:11 pm
Posts: 4592
Location: Switzerland
Ah yes, that's what I've waited for... I'm out, find somebody else to spoon feed you.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jul 12, 2006 6:51 am 
Newbie

Joined: Sat Jul 08, 2006 10:33 am
Posts: 15
I have to admit, that I'm hurt.

Some background. I'm in business for 20 years now, I'm developing with Java for 6, I've written big ORM apps based on TopLink or my own OR mappers. Just recently, I've decided to give Hibernate a try.

I bought the book, looked at the examples but I've had trouble with even simple cases (okay, simple for me ... starting with recursive tree/graph structures might not actually count as "simple", for some). Most of them, I could solve.

But one case out of 16 just didn't work. So I took an example from the documentation, checked it against my code and I couldn't find a differnce.

I reduced the test case, posted it as bug/documentation enhancement request, assuming that other people might have the same problem.

All in all, I spent two days before I decided to come here. I tried to explain the problem carefully, put a lot of effort in writing a good bug report.

Now this. "find somebody else to spoon feed you".

That's at least arrogant, at most an insult.

Is that how new Hibernate users are treated?

Maybe the many "stupid" questions (which seem to be asked here) are an indication that Hibernate is more complex than it could/should/has to be or that the documentation could use some improvement.

Until yesterday, I was thinking to put some effort into improving the docs but now, I'm not sure if it's worth the effort.

Like most people here, I'm doing this on my own and in my spare time and the net reusult must be positive and currently, it definitely isn't.

Sorry.

_________________
Aaron "Optimizer" Digulla
http://www.philmann-dark.de/


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 27 posts ]  Go to page 1, 2  Next

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.