-->
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.  [ 15 posts ] 
Author Message
 Post subject: Deep or Shallow: equals() and hashcode()?
PostPosted: Tue Feb 14, 2006 2:07 pm 
Newbie

Joined: Tue Sep 21, 2004 3:52 pm
Posts: 4
Hibernate wants me to implement equals() and hashcode() for my persistent classes. Fine. However, this seems to be at odds with lazy loading and collections.

For example:
Category:
String mNameString = "unnamed";
Set<Item> mItemSet = new HashSet<Item>();
Set<Category> mChildCategorySet = new HashSet<Category>();

Item:
String mNameString = "blah";
double mPrice = 0.0;

If category implements equals() to include comparison of the Set of Item objects contained, then lazy loading will be defeated since every object in my schema is pretty well connected.

If I don't implement a deep equals(), it would seem I'm violating the general equals() contract (Java of course defining equals in a way that makes sense and is simple if all objects are in memory and traversible) which will break deep Java assumptions.

I've noticed in the CaveatEmptor example (I'm looking at the most recent on published using the annotations version) does not do deep equals().

Thanks for your help,
greg


Top
 Profile  
 
 Post subject:
PostPosted: Tue Feb 14, 2006 2:55 pm 
Regular
Regular

Joined: Wed Feb 08, 2006 3:59 pm
Posts: 75
The correct equals semantic would be primary key comparaison, so no deep comparaison and no association lazy loading issues.

The goal is to see two objects have the same 'identity' (and so if one is an updated copy of the other one), no to see if the two objects are 'identical'


Top
 Profile  
 
 Post subject:
PostPosted: Tue Feb 14, 2006 3:33 pm 
Expert
Expert

Joined: Mon Jan 09, 2006 5:01 pm
Posts: 311
Location: Sacramento, CA
I would like to add a little here.

Equals, according to the book, should be implemented on a set of attributes that uniquely identifies the object, which is not necessarily the primary key (even though that is unique)... the point being is that transient objects (those with no primary key associated yet) may be equal to another object. The book further states that you can't rely on equals of primary key for generated keys, and must use business key equality.

One more item, you will have to implement equals/hashcode if you are dealing with "reattachement" of detached instances, or using "sets".

please give credit on this problem, if this helped. Thx.


Top
 Profile  
 
 Post subject:
PostPosted: Tue Feb 14, 2006 4:12 pm 
Newbie

Joined: Tue Sep 21, 2004 3:52 pm
Posts: 4
Right, that's the point.

I need guidance for equality.

In the case of Category above, I know the uniqueness constraints protect the database well. Once dettached (a major point of Hibernate), I now need to add extra equality semantics.

In the recursive relationship of Category, two Category instances are equal if and only if they have the same scalar values and relationships within the hierarchy.

Yet this makes things very expensive.

If CaveatEmptor doesn't have equals() defined for its collections, then it breaks the Java contract which in my mind breaks the idiomatic portion of Hibernate (I must treat Hibernate managed POJOs differently from others).

If there's no general solution, that's fine too but just want to know I shouldn't be looking for one and code around this permanently.


Top
 Profile  
 
 Post subject:
PostPosted: Tue Feb 14, 2006 4:44 pm 
Regular
Regular

Joined: Sat Nov 19, 2005 2:46 pm
Posts: 69
jt_1000 wrote:
Equals, according to the book, should be implemented on a set of attributes that uniquely identifies the object, which is not necessarily the primary key (even though that is unique)... the point being is that transient objects (those with no primary key associated yet) may be equal to another object. The book further states that you can't rely on equals of primary key for generated keys, and must use business key equality.


I take the viewpoint that transient objects, like null, are not equal to any other objects, even if they contain identical properties. I implement hashCode() so that transient objects use the default hashCode (ie the memory address of the java object) whilst persistent (and detached) objects are equal if their PK is equal. I do this to give me the control I need over objects in Sets and Maps.

If I want to check for true business equality, (for example, do two addresses actually refer to the same house) I might use a custom Comparator plus compareTo() == 0, but first I would prep some object clones with toUpperCase() and trim() and stripping punctunation, so that " 1 a Main St." really was equal to "1A, Main Street"

When I call equals() or put an entity into a HashSet() I want to know if the entity is itself, rather than if its data is the same as another entity. For example, two people living in a house share do have the same address, but in this case I really do want two seperate unique Address objects, one for each Person object.

See my post here for the implementation:
http://forum.hibernate.org/viewtopic.php?t=954210

_________________
Stewart
London, UK


Top
 Profile  
 
 Post subject: Re: Deep or Shallow: equals() and hashcode()?
PostPosted: Tue Feb 14, 2006 4:54 pm 
Expert
Expert

Joined: Mon Jan 09, 2006 5:01 pm
Posts: 311
Location: Sacramento, CA
ggerard wrote:
If category implements equals() to include comparison of the Set of Item objects contained, then lazy loading will be defeated since every object in my schema is pretty well connected.


Greg,
I think this is the heart of your question...right?
Well I am not sure why you say that lazy loading is defeated - My understanding is that Lazy loading is only meant for defering the loading of that object until it is referenced by the parent... in otherwords, it will be loaded when you reference objects for that set; and at that point hibernate will load and compare (equals..etc.) the objects.

Please help clarify your concern.

Thanks,
JT


Top
 Profile  
 
 Post subject:
PostPosted: Tue Feb 14, 2006 8:38 pm 
Newbie

Joined: Tue Sep 21, 2004 3:52 pm
Posts: 4
Obviously lazy loading isn't defeated functionally (it still loads as you navigate the collections for the equals/hashCode calls), but rather the benefit of lazy loading is lost in that I'm now dragging in a ton of data from the database potentially (hopefully all the caching layers keep it to a minimum but I'm trying to establish a worst case impact and design accordingly).

Is this a bug I should be reporting against CaveatEmptor? They recommend overriding equals()/hashCode() but do so in a way that violates the Java Object contract for those two methods.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 15, 2006 7:22 am 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 6:10 am
Posts: 8615
Location: Neuchatel, Switzerland (Danish)
There are so many factual errors in this posting so i'm forced to correct it:

Error #1: Hibernate wants me to implement equals() and hashcode() for my persistent classes.

No, Hibernate does not want you to do this, it is the contract of java Collections that require this! For some reason it is normally when people first start using Hibernate they realize that they have been using java collections dangerously before.

Error #2: " I implement hashCode() so that transient objects use the default hashCode (ie the memory address of the java object) whilst persistent (and detached) objects are equal if their PK is equal"

That will result in an implementation of equals/hashcode that will change behavior while it is in e.g a Set - that is BAD unless you can guarantee that your objects never are in a set ...but then why have this weird behavior in the first place ?

Error #3: "The correct equals semantic would be primary key comparaison"
Just as wrong as Error #2

Guys please read, understand and learn from http://www.hibernate.org/109.html

_________________
Max
Don't forget to rate


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 15, 2006 8:15 am 
Regular
Regular

Joined: Sat Nov 19, 2005 2:46 pm
Posts: 69
max wrote:
Error #2: " I implement hashCode() so that transient objects use the default hashCode (ie the memory address of the java object) whilst persistent (and detached) objects are equal if their PK is equal"

That will result in an implementation of equals/hashcode that will change behavior while it is in e.g a Set - that is BAD unless you can guarantee that your objects never are in a set ...but then why have this weird behavior in the first place ?

Error #3: "The correct equals semantic would be primary key comparaison"
Just as wrong as Error #2

Guys please read, understand and learn from http://www.hibernate.org/109.html


Max, your assumption about my ignorance is incorrect. I have read (several times in recent weeks) the above article and believe I understand it fully and correctly. My hashCode() implementation solution came directly out of studying that article and related articles.

I concede I had not mentioned all of the context of my solution, however, if you had taken the time to look at the link I provided (same as I took the time to follow your link) you'd have seen that this hashCode() doesn't change ever per object, because it is lazily instantiated:
Code:
private Integer hashCode = null;
public int hashCode() {
  if(hashCode == null) {
    hashCode = new Integer( /* calc your hash code here */ );
  }
  return hashCode.intValue();
}


Only time the "object changes" is when it is actually a new object, such as between one Session closing and another opening. This happens with each HttpRequest as I'm using a variation on "open session in view". This has required that I "re-bind" (using session.merge() or session.lock()) objects stored in the HttpSession anyway. Because of this it has not been a problem. I have many objects in many Sets and they are all behaving very correctly.

Your pointing out of an error flies in the face of the fact that my app is working very well indeed. Maybe there is some potential danger which I have not spotted and which luck kept away from me?
I do thank you for your interest. It's a subject loaded with traps for the unwary.

_________________
Stewart
London, UK


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 15, 2006 8:59 am 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 6:10 am
Posts: 8615
Location: Neuchatel, Switzerland (Danish)
There were one correct posting in this post (besides mine ;), and that were the third answer by the-gtm.

So where is the problem in your impl ?

Your equals method has to be in sync with what the hashcode returns - how do you prevent it from being equal to something that has the same primarykey, but which hashcode were calculated before it got a primarykey ?

x.hashcode();
session.save(x);
flush/commit/etc.

otherx = session2.load(x.class, x.id)

otherx.equals(x) but x.hashcode()!=otherx.hashcode() meaning if x is in a set somewhere set.contains(otherx) will be false even though it is not.

_________________
Max
Don't forget to rate


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 15, 2006 9:24 am 
CGLIB Developer
CGLIB Developer

Joined: Thu Aug 28, 2003 1:44 pm
Posts: 1217
Location: Vilnius, Lithuania
One of "best OOP practices": "new" object must be valid. Factory method can be used to ensure it:

Code:
public class MyPojo {

    int id;//generated

    /*package*/ MyPojo(){}

   }

/*
factory per package
*/
public class MyPojoFactory{

   MyPojo newMyPojoInstance( <<all required attributes in parameter list>> ){
       .....
  }


}


Probably it is a bit ideological, but it must help to avoid mistakes.
It will force to flush session per instance creation with "native" generator or access sequence directly and use "assigned" for generated id.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 15, 2006 10:02 am 
Regular
Regular

Joined: Sat Nov 19, 2005 2:46 pm
Posts: 69
max wrote:
Your equals method has to be in sync with what the hashcode returns - how do you prevent it from being equal to something that has the same primarykey, but which hashcode were calculated before it got a primarykey ?

x.hashcode();
session.save(x);
flush/commit/etc.

otherx = session2.load(x.class, x.id)

otherx.equals(x) but x.hashcode()!=otherx.hashcode() meaning if x is in a set somewhere set.contains(otherx) will be false even though it is not.


This is indeed a very valid point, and a very real problem.

It is to some degree a point covered in the comments I wrote in the other thread:
Code:
/**
* .....
* If an object is in a HashSet (or similar) prior to being newly persisted, it will at least be recoverable
* by using the exact same object reference, but will not be recoverable by a new reference created using session.load(Class, Serializable).
* .....
*/


In my app this is addressed with my "re-binding" semantics. session.flush() only occurs at the end of the HttpRequest, and therefore x will always be detached at this point.
If x is stored in the HttpSession (or a Collection/Map thereof) then it found and refreshed when the next HttpRequest starts, using session.merge() if the session is to be R/W or with session.get() if the session is to be R/O.
If x is not picked up in this way, then it remains "stale" - it does indeed brake the hashCode(), but much much worse than this, it is a duplicate object which has the potential to throw a NonUniqueObjectException if it becomes involved with any part of a persistent parent/child tree. In other words, the object is useless, and I throw it out into the garbage.

Ok, I looked it over and I concede. Without that "re-bind" activity, this kind of hashCode() could be dangerous.

_________________
Stewart
London, UK


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 15, 2006 10:13 am 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 6:10 am
Posts: 8615
Location: Neuchatel, Switzerland (Danish)
exactly, and since it is very suttle i came on to you guys hard because the statements were either directly false (e.g. hibernate requires you to implement equals/hashcode) or half truths with provided solutions that only works under some preconditions not really mentioned....

this is why i created 109.html in the first place - to get it correctly covered in one place.

_________________
Max
Don't forget to rate


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 15, 2006 2:38 pm 
Expert
Expert

Joined: Mon Jan 09, 2006 5:01 pm
Posts: 311
Location: Sacramento, CA
pardon me, IMHO I think you guys (Stewart and Max) went on a little tangent. I (remember I am still a newbie) have a few still remaing questions.

1) Max - why was my posting incorrect ...?

2) how is the original question posted by Greg addressed - not even sure I understand what his concern still is. After all, lazy loading is just that.. lazy until you access the set - perhaps this is leading to a case for READ-ONLY reference data, loaded ahead of time in 2nd level cache; and would automatically be available to the set (and I would further assume that the Lazy flag would be ignored.) If he is talking about reinitializing the "set" with transient objects, then of course he'll have to implement equals/hashcode...Lastly, if he is talking about loading the uninitialized objects directly from database, then it falls back to Lazy loading semantics working correctly - they won't be loaded until accessed...right?
So what is up?

3) having read the 109.html article - I have one question here: why is the flush required "after object creation and before insertion into the set:"
session.flush(); // The id is now assigned to the new User object
In my experience the generated IDs are assigned at the save() statement.
(one point of clarification on this question #3 - I see we are talking about user assigned ID's and not generated ones - but the question still remains -- why the flush? Ideally should hibernate know its ID based on the hashCode, as it is added to the set?)

Thanks for your assistance Max.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 15, 2006 3:05 pm 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 6:10 am
Posts: 8615
Location: Neuchatel, Switzerland (Danish)
sorry - the names were missaligned.

it was the third answer by you: jt_1000 that were correct not the-gtm.

id's are not assigned on cascaded collections until flush for save()

_________________
Max
Don't forget to rate


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 15 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.