Equals and hashcode
Java's Collections and Relational database (and thus Hibernate) relies heavily on being able to distinguish objects in a unified way. In Relational database's this is done with primary keys, in Java we have equals () and hashCode () methods on the objects. This You Persistent Classes.
Why is equals () and hashcode () important
.
This is generally what you want in ordinary Java programming. And if all your objects are in memory, this is a fine model. Hibernate's whole job, of course, is to move your objects out of memory. But Hibernate works hard to prevent you from Having to worry about this.
Hibernate uses the Hibernate session to manage this uniqueness. When you create an object with new (), and then save it into a session, Hibernate now knows that whenever you query for an object and find that particular object, Hibernate should return you that instance The Object. And Hibernate Will Do Just That.
However, once you close the Hibernate session, all bets are off. If you keep holding onto an object that you either created or loaded in a Hibernate session that you have now closed, Hibernate has no way to know about those objects. So if you open another session and query for "the same" object, Hibernate will return you a new instance. Hence, if you keep collections of objects around between sessions, you will start to experience odd behavior (duplicate objects in collections, mainly).
The general contract is: if you want to store an object in a List, Map or a Set then it is an requirement that equals and hashCode are implemented so they obey the standard contract as specified in the documentation.What is the problem after all?
.
The most natural idea that comes to mind is implementing equals () and hashCode () by comparing the property you mapped as a database identifier (ie. The primary key attribute). This will cause problems, however, for newly created objects, because Hibernate Sets The Identifier Value for You After Storing New Objects. Each New Instance ThereFore Has The Same Identifier, NULL (or
// suppose UserManager and User Are Beans Mapped with Hibernate
UserManager u = session.load (usermanager.class, id);
u.getuserset (). Add ("new user (" newusername1 ")); // adds a new entry = null or id = 0
u.getuserset (). add ("new user (" newusername2 ")); // HAS ID = NULL, TOO, SO overwrites Last Added Object.
// u.getuserSet () Now Contains Only the second user
As you can see relying on database identifier comparison for persistent classes can get you into trouble if you use Hibernate generated ids, because the identifier value will not be set before the object has been saved. The identifier value will be set when session.save () IS Called On Your Transient Object, Making IT Persistent.
If you use manually assigned ids (eg the "assigned" generator), you are not in trouble at all, you just have to make sure to set the identifier value before adding the object to the Set. This is, on the other hand, Quite Difficult To Guarantee In Most Applications.seperating Object Id and Business Key
To avoid this problem we recommend using the "semi" -unique attributes of your persistent class to implement equals () (and hashCode ()). Basically you should think of your database identifier as not having business meaning at all (remember, surrogate identifier attributes and automatically generated vales are recommended anyway). The database identifier property should only be an object identifier, and basically should be used by Hibernate only. Of course, you may also use the database identifier as a convenient read-only handle, eg to Build Links in Web Applications.
Instead of using the database identifier for the equality comparison, you should use a set of properties for equals () that identify your individual objects. For example, if you have an "Item" class and it has a "name" String and "created "Date, I can use both to implement a good equals () method. No need to use the persistent identifier, the so called" business key "is much better. It's a natural key, but this time there is nothing wrong in using it !
The combination of both fields is stable enough for the life duration of the Set containing your Items. It is not as good as a primary key, but it's certainly a candidate key. You can think of this as defining a "relational identity" for your object - the key fields that would likely be your UNIQUE fields in your relational model, or at least immutable properties of your persistent class (the "created" Date never changes) .In the example above, you could probably use the "username" Property.
Note That this is all this you have to know about equals () / hashcode () in Most Cases. If you read on, you might find solutions this don't work perfectly or suggestions this don't help you much. Use any of The Following at your OWN Risk.
Workaround by forcing a save / flush
If you really can not get around using the persistent id for equals () / hashCode (), and if you really have to keep objects around from session to session (and hence can not just use the default equals () / hashCode ( ))
// suppose UserManager and User Are Beans Mapped with Hibernate
UserManager u = session.load (usermanager.class, id);
User newuser = new user ("newusername1");
// u.getuserset (). Add (newuser); // do not add to set yet!
Session.save (newuser);
Session.flush (); // the iS now assigned to the new user object
u.getuserSet (). add (newuser); // now ok to add to set.
Newuser = New User ("NewUserName2");
Session.save (newuser);
Session.flush ();
u.getuserset (). add (newuser); // Now userset contains Both Uses.
Note That it's highly inefficient and thus not recommended. Also Note That IT IS Fragile WHEN Using Disconnected Object Graphs On A Thin Client: // On Client, Let's Assume The UserManager IS EMPTY:
UserManager u = userManagerSessionBean.Load (userManager.class, id);
User newuser = new user ("newusername1");
u.getuserset (). Add (newuser); // Have to add it to set now sinceclient cannot save it
UserManagerSessionBean.UpdateUserManager (u);
// ON Server:
UserManagerSessionBean UpdateUserManager (UserManager U) {
// get the first user (this Example Assumes there is only one)
User newuser = (user) u.getuserSet (). Itrator (). Next ();
Session.saveorupdate (u);
IF (! u.getuserset (). Contains (newuser)) System.err.Println ("User Set Corrupted.");
}
This Will Actually Print "User Set Corrupted." Since Newuser's Hashcode Will Change Due To The SaveorUpdate Call.
This is all frustrating because Java's object identity seems to map directly to Hibernate-assigned database identity, but in reality the two are different - and the latter does not even exist until an object is saved The object's identity should not depend on. .
IT's Bothersome To Write these Methods, Can't Hibernate Help?
Well, The Only "Helping" Hand Hibernate Can Provide Is HBM2JAVA.
. Hbm2java generates a default implementation of equals () and hashcode () which relies on id comparison After all hbm2java has no way to know your unique buisness key Often, you will want plain old Java equality rather than persistent-id equality;. There is a JIRA issue that you can vote for if you want to enable hbm2java to not generate its default implementation.In the future we might provide a feature in hbm2java that allows you to mark which properties make up the unique identity of your persistent class; this would Support the "Candidate Key" Type of Identity.
Summary
To Sum All this Stuff Up, Here Is A Listing of What Will Work or Way 'To Handle Equals / Hashcode:
no eq / hC at all eq / hC with the id property eq / hC with buisness keyuse in a composite-id No Yes Yesmultiple new instances in set Yes No Yesequal to same object from other session No Yes Yescollections intact after saving Yes No Yes
WHERE The Various Problems Are As Follows:
Use in a commits-id:
To use an Object as a commit, it has inplement equplement / hashcode in some way, == identity will not be enough.
Multiple new instances in set:
Will The Following Work or Not:
Hashset someset = new hashset ();
SomeSet.Add (New PersistentClass ());
SomeSet.Add (New PersistentClass ());
assert (somester.size () == 2);
Equal to Same Object from another session:
Will The Following Work or Not:
PersistentClass P1 = sessionone.load (PersistentClass.class, New Integer (1));
PersistentClass P2 = sessionTwo.load (PersistentClass.class, New Integer (1));
Assert (p1.equals (p2));
Collectes IntactAfter saving:
Will The Following Work or Not:
Hashset set = new hashset ();
User u = new user (); set.add (u);
Session.save (u);
Assert (Set.Contains (U));
Any Best Practicies for Equals and hashcode
Read The Links in 'Background Material' and the API Docs - The Yy Provide The Gory Details.
Furthermore I encourage anyone with information and tips about equals and hashcode implementations to come forward and show their "patterns" - I might even try to incorporate them inside hbm2java to make it even more helpful;)
Background Material:
Effective Java Programming Language Guide, Sample Chapter About Equals () and hashcode ()
Java THEORY AND PRACTICE: HASHING IT OUT, ARTICLE FROM IBM
Sam Pullara (BEA) Comments On ON Object Identity: Blog Comment
Article About How To Implement Equals and Hashcode Correctly by Manish Hatwalne: Equals and Hashcode
Forum Thread Discussing Implementation Possibilities WITHOUT Defining a Business Identity: Equals and Hashcode: Is there * any * non-broble approach?
http://www.hibernate.org/109.html