Equals and HashCode

http://www.hibernate.org/109.html

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 page tries to discuss the best strategies for implementation of equals() and hashcode() in your persistent classes.

Why are equals() and hashcode() important

Normally, most Java objects provide a built-in equals() and hashCode() based on the object's identity; so each new() object will be different from all others.

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 of 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?

So let's say you do want to keep objects around from session to session, e.g. in a Set related to a particular application user or some other scope that spans several Hibernate sessions.

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 <literal>0</literal>). For example, if you add some new objects to a Set:

// 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 Entity with id = 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 won't 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 (e.g. 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.

Separating 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, e.g. 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 that you have to know about equals()/hashCode() in most cases. If you read on, you might find solutions that don't work perfectly or suggestions that don't help you much. Use any of the following at your own risk.

Workaround by forcing a save/flush

If you really can't 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't just use the default equals() / hashCode()), you can work around by forcing a save() / flush() after object creation and before insertion into the set:

// 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 id 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 users.

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 since client cannot save it
userManagerSessionBean.updateUserManager(u);

// on server:
UserManagerSessionBean updateUserManager (UserManager u) {
  // get the first user (this example assumes there's only one)
  User newUser = (User)u.getUserSet().iterator().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 doesn't even exist until an object is saved. The object's identity shouldn't depend on whether it's been saved yet or not, but if your equals() and hashCode() methods use the Hibernate identity, then the object id does change when you save.

It's bothersome to write these methods, can't Hibernate help?

Well, the only "helping" hand Hibernate can provide is hbm2java.

hbm2java does not (anymore) generate equals/hashcode based on id's because of the described issues in this page.

You can though mark certain properties with <meta attribute="use-in-equals">true</meta> to tell hbm2java to generate a proper equals/hashcode.

Summary

To sum all this stuff up, here is a listing of what will work or won't work with the different ways to handle equals/hashCode:

 no eq/hC at all eq/hC with the id property eq/hC with buisness key
use in a composite-id No Yes Yes
multiple new instances in set Yes No Yes
equal to same object from other session No Yes Yes
collections intact after saving Yes No Yes

Where the various problems are as follows:

use in a composite-id:

To use an object as a composite-id, it has to implement equals/hashCode in some way, == identity will not be enough in this case.

multiple new instances in set:

Will the following work or not:

HashSet someSet = new HashSet();
someSet.add(new PersistentClass());
someSet.add(new PersistentClass());
assert(someSet.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));

collections intact after 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 - they 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 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-broken approach?

According to The java.lang.Object documentation it should be perfectly ok to always return 0 for the hashCode(). The positive effect of implementing hashCode() to return unique numbers for unique objects, is that it might increase performance. The downside is that the behavior of hashCode() must be consistent with equals(). For object a and b, if a.equals(b) is true, than a.hashCode() == b.hashCode() must be true. But if a.equals(b) returns false, a.hashCode() == b.hashCode() may still be true. Implementing hashCode() as 'return 0' meets these criteria, but it will be extremely inefficient in Hash based collection such as a HashSet or HashMap.


 NEW COMMENT

Easier implementation using commons-lang 16 Jan 2004, 12:07 thorstenschaefer
Hi,

The commons-lang package from apache contains builder for equals and 
hashCode (beside others) which can be used for efficent and straight-
forward implementation of these methods.

Thorsten
 
Re: Easier implementation using commons-lang 16 Jan 2004, 13:20 lhotari
On 16 Jan 2004 12:07, thorstenschaefer wrote:

>Hi,

>The commons-lang package from apache contains builder for equals and
>hashCode (beside others) which can be used for efficent and straight-
>forward implementation of these methods.

http://commonclipse.sourceforge.net/ is an eclipse plugin that generates
code for commons-lang builders.
 
hbm2java bug? 10 Feb 2004, 15:57 gruberc
So, according to the Best Practice advice, is it a good idea for
hbm2java to automatically take the ID for the automatic creation of the
equals() and hashCode() methods? The only modificator of the code
creation I have seen is the "use-in-toString" attribute.

Christian
 
hbm2java and best practice.. 12 Feb 2004, 17:04 hooverphonique
You discourage the use of the id (primary key attribute) in the equals 
method, but hbm2java does exactly that.. does that mean that you 
discourage the use of hbm2java and/or the apache builders?
 
Re: hbm2java and best practice.. 12 Feb 2004, 19:11 emmanuel
On 12 Feb 2004 17:04, hooverphonique wrote:

>You discourage the use of the id (primary key attribute) in the equals
>method, but hbm2java does exactly that.. does that mean that you
>discourage the use of hbm2java and/or the apache builders?

We encourage the hand writing of equals and hashcode. You can use apache
builder to do that.
 
Most Tables can only use Identity Property for equals/hashCode! 14 Feb 2004, 14:21 Goonie
There are so many persistant classes with the identity property (primary
key) being the only feasible identifier. To be exact, only classes whose
tables have an additional unique constraint allow for safely using other
properties than the identity for equals/hashCode.

Unless I do misunderstand the matter, I have only two choices for about
99% of my persistant classes:

- using self assigned identities (primary keys) and assign them before
using the entity in a collection.

- using database assigned identites and forcing early assignment by
session.save().

Any other choices?

Regards,

Andreas
 
fallback to object identiy 16 Feb 2004, 16:02 hengels
what about using the primary key and fall back to object identity if a
pk is not assigned?
 
Re: fallback to object identiy 26 Feb 2004, 19:13 RobJellinghaus
On 16 Feb 2004 16:02, hengels wrote:

>what about using the primary key and fall back to object identity if a
>pk is not assigned?

This breaks collections because it changes the object's hashcode once
you save it.  So you would get:

UserManager u = session.load(UserManager.class, id);

User newUser = new User("newUsername1");
u.getUserSet().add(newUser); // adds a new Entity with id = null or id = 0
session.save(newUser);
session.flush();             // The id is now assigned to the newUser object
if (!u.getUserSet().contains(newUser)) System.err.print("OOPS!");

This *will* print "OOPS!" because newUser's hashcode changed when you
saved it, so the userSet can no longer find it.

See this forum thread http://forum.hibernate.org/viewtopic.php?t=928172
for an exhaustive discussion of this problem.
 
Re: hbm2java and best practice.. 26 Feb 2004, 19:14 RobJellinghaus
On 12 Feb 2004 19:11, emmanuel wrote:

>On 12 Feb 2004 17:04, hooverphonique wrote:

>>You discourage the use of the id (primary key attribute) in the equals
>>method, but hbm2java does exactly that.. does that mean that you
>>discourage the use of hbm2java and/or the apache builders?

>We encourage the hand writing of equals and hashcode. You can use apache
>builder to do that.

hbm2java needs better support for this, then, and the hbm2java
documentation needs to reference this wiki page, as it's totally not
obvious to any hbm2java user right now that this issue exists.
 
Re: Easier implementation using commons-lang 26 Feb 2004, 19:15 RobJellinghaus
On 16 Jan 2004 13:20, lhotari wrote:

>On 16 Jan 2004 12:07, thorstenschaefer wrote:

>>Hi,

>>The commons-lang package from apache contains builder for equals and
>>hashCode (beside others) which can be used for efficent and straight-
>>forward implementation of these methods.

>http://commonclipse.sourceforge.net/ is an eclipse plugin that generates
>code for commons-lang builders.

This issue has nothing to do with *how* you build the methods, and
everything to do with *which properties you use* to build the methods. 
Hence, commons-lang can't help with the fundamental issue of defining
your identity fields (which can't be either plain old Java identity, nor
plain old persistent identity!).
 
Re: hbm2java and best practice.. 01 Mar 2004, 22:45 christian
On 12 Feb 2004 17:04, hooverphonique wrote:

>You discourage the use of the id (primary key attribute) in the equals
>method, but hbm2java does exactly that.. does that mean that you
>discourage the use of hbm2java and/or the apache builders?

hbm2java has _configurable_ support for equals()/hashCode() generation.
The default included the database identifer, which wasn't a good idea.
This is now fixed in CVS and in the next release of Hibernate Extensions.
 
Re: fallback to object identiy 15 Mar 2004, 15:06 hengels
On 26 Feb 2004 19:13, RobJellinghaus wrote: 
 
>On 16 Feb 2004 16:02, hengels wrote:
 
>>what about using the primary key and fall back to object identity if
a 
>>pk is not assigned?
 
>This breaks collections because it changes the object's hashcode once
>you save it.  So you would get:
 
>UserManager u = session.load(UserManager.class, id);
 
>User newUser = new User("newUsername1");
>u.getUserSet().add(newUser); // adds a new Entity with id = null or
id = 0 
>session.save(newUser);
>session.flush();             // The id is now assigned to the newUser
object 
>if (!u.getUserSet().contains(newUser)) System.err.print("OOPS!");
 
>This *will* print "OOPS!" because newUser's hashcode changed when you
>saved it, so the userSet can no longer find it.
 
Huh! Then we need a rehash method on Set and Map ;-) ... or at least 
we could simulate a rehashing by removing and readding all entries.
 
Proxies! 19 Apr 2004, 17:08 melquiades
This page also needs to address proxies, which were the cause of most of
the discussion on this thread:

http://forum.hibernate.org/viewtopic.php?t=928172

Two very important conclusions came out of that thread: (1) you *must*
override eq/hc to use proxies, even if you don't mix objects from
different sessions, and (2) there is no way to cause eq/hc *not* to
inflate proxies, even if you're using a synthetic PK as the basis of
comparison.
 
Unit Tests 22 Sep 2004, 07:06 cote
When using collections in Hibernate, specifically bags for many-to-many
relationships, what's the best way to go about getting assertEquals to work.

For example, if how could I get simple "see if updating works" code
along the lines of the below to pass the final assertEquals?

List children = new ArrayList(2);
children.add(child1);
children.add(child2);
parent.setChildren(children);
parent.addChild(child2);

Parent stored = session.find("from Parent where id='dad'");
// getChildren() returns a Bag, which isn't equal to the
// above ArrayList children.


// returns false, failing test
assertEquals(parent, stored);
 
Using properties of an object for equals and hash code is broken 26 Jan 2005, 16:45 andrewGoedhart
The reccomendation of using the properties of an object rather then 
the object identity and or synthetic primary key is just as broken as 
the problem we are trying to highlight and solve. This is because if 
any of the properties used in the composite key are edited all the 
problems listed by the main author will occur. In some cases it could 
be worse because the problems associated with changes to the 
properties making up the new key will most likely only show up during 
production. 

The solution actually lies in hibernates contract of maintaining a 
single instance per object in the session cache. 

Equals is easy:

public boolean equals(Object other){
 if( this == other) 
    return true;
 if( (this.id == null) || (other.id== null))
   return false;
 return this.id.equals(other.id);
}

Here we assume that new objects are always different unless they are 
the same object. If an object is loaded from the database it has a 
valid id and therefore we can check against object ids.

Hash code is more difficult we need to ensure that if:
A==B 
then 
A.hashcode == B.hashcode

If we base it on Ids which dont change once assigned then we only have 
to ensure that new objects also obey the above contract within the 
scope of the session. 


private Integer hashcodeValue = null;

public synchronized int hashCode(){
  if( hashcodeValue == null){
    if( id == null){
      hashcodeValue = new Integer(super.hashcode());
    } else {
      hashcodeValue = generateHashCode(id);
    }
  }
  return hashcodeValue.intValue();
}

What we are doing here is ensuring that once a hshcode value is used, 
it never changes for this object. This allows us to use object 
identity for new objects and not run into the problems listed above. 
In fact the only case where this is a problem is when we save a new 
object, keep it around after we close the session, load a new instance 
of the object in a new session and then compare them. 

in this case we get A==B but a.hashcode != b.hashcode

The above functions work in all other scenarios and don't lead to 
broken implementations when the propety of the object are edited. The 
whole point in generating synthetic primary keys in the first place is 
to avoid having a primary key which is dependant on an object  
property and which therefore may change during the life time of the 
object.
 
Re: Using properties of an object for equals and hash code is br 05 Feb 2005, 02:50 Ras_Nas
On 26 Jan 2005 16:45, andrewGoedhart wrote:

>The reccomendation of using the properties of an object rather then
>the object identity and or synthetic primary key is just as broken as
>the problem we are trying to highlight and solve.

The above statement is broken because using "the object identity and 
or synthetic primary key" and "the problem" are equivalent.

Everything you wrote takes us back to square one (you pointed out very 
well the problem with your alternate scheme) because you missed the 
phrase 'immutable properties' that the original author used.

>The whole point in generating synthetic primary keys in the first
>place is to avoid having a primary key which is dependant on an
>object property and which therefore may change during the life
>time of the object.

Is it so? From a 'relational' world perspective, is immutability a
requirement for a primary key?
What's "primary keys" got to do with "object property" when there are
no objects in the relational world?
If there were, would we need Hibernate?

Rico.
 
Re: Most Tables can only use Identity Property for equals/hashCo 09 Feb 2005, 07:38 tibi
>Unless I do misunderstand the matter, I have only two choices for about
>99% of my persistant classes:


i must agree most of my objects have only flieds which can be changed or
are not unique.

example a user has a login (email address) and a password both fields
should be changable. do i need to ask the user to give a extra unique
string so i can keep my objects apart?


would it be an idea to add my own id on object creation time.
this way i have a unique key which never is empty and don't need to
change. only problem is when objects get created in different vm's.
 
immutable property 16 May 2005, 11:50 maulinshah
I'm not sure what the best answer is. But essentially, my analysis boils
down to the fact that I could use the database identifier if I could get
it. But I can't get it when I need it (before assigning my object to a
set). So instead I have to use a business surrogate which uses an
immutable property. Most of my objects have almost entirely MUTABLE
properties, except for maybe the "created" date (as mentioned above).
But that still doesn't sit right in my stomach. I think the chance of an
inadvertent duplicate using "created" is too high.

So my solution. I created a uid field in my BasePersitentObject. its
setter is private (only to be used by Hibernate). its getter is
protected (though I could probably make it private too). It is assigned
when the object is created (new), and persisted with the object, so it
can be reloaded when objects are reloaded.

The uid function I use was posted on
http://www.javaexchange.com/aboutRandomGUID.html

The base class implements hashCode and equals, and though they are
public, I do not want any of my peristent objects to override them,
since I can't imagine that they will come up with a better immutable
hashCode/equals function.

The other advantage of this system is that when I load in data from my 
legacy system, I can make the uid = the id of the OLD data, since that
is guaranteed to be unique across a table.

That being said (and this seems to work well), this makes business
equivalence break for me. For example, if I have two last names, and a
LastName persistent object (which would never happen, but you get my
point), they should be equivalent if "Smith" and "Smith", but this would
make those not equal, which is correct. They are equivalent but not equal. 

Enough rambling...
 
Our approach: Don't bother 18 Jul 2005, 16:06 rhasselbaum
We made a conscious decision NOT to override the default 
implementations of equals and hashCode, and rely exclusively on object 
identity. There are the known limitations, of course, but they haven't 
been much of a problem in practice. We rarely have a need to "mix" two 
objects with the same database identity loaded from two different 
sessions. And when we do, we create an inner class that encapsulates 
the business key from the entity and use that as the key into maps and 
such.

It may not address all cases perfectly, but it's consistent and easy 
to follow.
 
Re: Easier implementation using commons-lang 20 Jul 2005, 02:15 crbaker
On 16 Jan 2004 13:20, lhotari wrote:

>On 16 Jan 2004 12:07, thorstenschaefer wrote:

>>Hi,

>>The commons-lang package from apache contains builder for equals and
>>hashCode (beside others) which can be used for efficent and straight-
>>forward implementation of these methods.

>http://commonclipse.sourceforge.net/ is an eclipse plugin that generates
>code for commons-lang builders.

The only problem I've found with commonclipse is that its references
member fields rather than getters and setters which can cause the
following issue.

Problem occurs when referencing objects internal field values with
Hibernated proxies. As the equals method has access to these private
fields it is only natural to directly access the fields. The problem is
that hibernate proxies will only fetch the fields value on request from
the fields getter.
incorrect equals()

/**
     *  (at) see java (dot) lang.Object#equals(Object)
     */
    public boolean equals(Object object)
    {
        if (!(object instanceof User))
        {
            return false;
        }
        User rhs = (User) object;
        return new EqualsBuilder().append(this.username,  
rhs.username).isEquals();
    }

correct equals()

/**
     *  (at) see java (dot) lang.Object#equals(Object)
     */
    public boolean equals(Object object)
    {
        if (!(object instanceof User))
        {
            return false;
        }
        User rhs = (User) object;
        return new EqualsBuilder().append(this.getUsername(),
rhs.getUsername()).isEquals();
    }
 
Oid are almost the time best candidates for equals() 05 Aug 2005, 14:35 jerome_angibaud
See this article
http://djeang.blogspot.com/2005/08/override-equals-and-hashcode-methods.html
 
I use UUIDs as object identifiers 14 Oct 2005, 00:12 gschadow
When using a UUID as the object identifier, you can generate the IDs
right away and don't neet the Hibernate session to create the id for
you. If you're worried about size, it's a 128 bit binary, that's 16 bytes.

I am wondering why this article makes such a big deal of not using the
object identifier for the equals and hash code, if it is so easy with a
UUID???
 
Re: fallback to object identiy 31 Jan 2006, 13:56 Mr_Light
POST QUESTIONS ON THE FORUM! COMMENTS HERE SHOULD ADD VALUE TO THE
PAGE!On 15 Mar 2004 15:06, hengels wrote:

>On 26 Feb 2004 19:13, RobJellinghaus wrote:
>
>>On 16 Feb 2004 16:02, hengels wrote:
>
>>>what about using the primary key and fall back to object identity if
>a
>>>pk is not assigned?
>
>>This breaks collections because it changes the object's hashcode once
>>you save it.  So you would get:
>
>>UserManager u = session.load(UserManager.class, id);
>
>>User newUser = new User("newUsername1");
>>u.getUserSet().add(newUser); // adds a new Entity with id = null or
>id = 0
>>session.save(newUser);
>>session.flush();             // The id is now assigned to the newUser
>object
>>if (!u.getUserSet().contains(newUser)) System.err.print("OOPS!");
>
>>This *will* print "OOPS!" because newUser's hashcode changed when you
>>saved it, so the userSet can no longer find it.
>
>Huh! Then we need a rehash method on Set and Map ;-) ... or at least
>we could simulate a rehashing by removing and readding all entries.

actually the way I read
http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Object.html#hashCode() 

"the hashCode method must consistently return the same integer, provided
no information used in equals comparisons on the object is modified." 
so provided it isn't the same the integer returned by hashCode() is
allowed to be differend.

"This *will* print "OOPS!" because newUser's hashcode changed when you 
saved it, so the userSet can no longer find it." I expect nothing less tbh.

Because: aren't we going about it the wrong way? Should one alter the
set directly? Yes, no? Ok, let's go with maybe. Now, lets go one step
down, what is a Set(or other Collection)? it's just an collection(you
might be thinking I'm waisting your time but bare with me here). what
has this got to with the entity from which we obtained it? (Other then
that we obtained it from that entity?) nothing! why do we expect magic
to happen? and why would the computer know all this crap and why should
it? If we want to make the set behave differendly from a normal set we
should change how it reacts.

how do we normaly change how an object(better: class?) reacts in the OO
world of today? we either extend the class, wrap/encapsulate it in an
other class etc.

Well extending a Set for every entity is not the way to go(for reasons
obvious I hope) so what about wrapping? yes? no? Lets see don't we
already have it wrapped in that Entity? and what was the other thing we
need? oh yes that relation to the entity, man it all comes togetter. 

instead of calling add() methodes on the list directly create an
addFoo() in which you check for recordUniqueness etc. (in other words
checks if your objects are uqiue on a database record level, and that it
doesn't conflict with constrains) sure you still want to retrive Set's
List's and all that in your code no problem just don't manipulate them,
for savety wrap them in a list that throws
UnsupportedOperationException's if you want to constrain yourself(I would)

in short:
your best stratigy is to make equals and hashcode behave as discribed in
the documentation of Object. and use a differend methode(other then
equal()) altogetter to determent Database record uniqueness. and build
your rules upon that not abuse the equals/hashcode and break it. And a
set should behave as a set.

I hope someone can follow me.
 
Use UUID as primary key or opaque field 05 Jun 2006, 08:26 fwolff
I aggree with gschadow on the use of UUIDs.

To be more precise: the all problem comes with mutability of the
hashCode and equals functions, in conjunction with the behavior of
(hashed or not) collections.

There are two options:

1. Use UUIDs as primary key (NOT generated by Hibernate) and implements
hashCode and equals functions as relying on object identity:

(a la EJB3...)

@MappedSuperclass
public abstract class BaseEntity implements java.io.Serializable {

    @Id
    private String id; // Could be a numeric type...

    public BaseEntity() {
        this.id = java.util.UUID.randomUUID().toString()
    }

    ...

    @Override
    public int hashCode() {
        return id.hashCode();
    }

    @Override
    public boolean equals(Object o) {
        return (o == this || (o instanceof BaseEntity &&
id.equals(((BaseEntity)o).id));
    }
}

Whether this kind of enitity is saved or not, hashCode value will be
always the same.

2. (if you worry about primary key efficiency) Use UUIDs as persistent
immutable field:

@MappedSuperclass
public abstract class BaseEntity implements java.io.Serializable {

    @Column(name="__UUID__", unique=true, nullable=false,
updatable=false, length = 36)
    private String internalUUID;

    @Id @Generated
    private Integer id;

    public BaseEntity() {
        this.internalUUID = java.util.UUID.randomUUID().toString()
    }

    ...

    @Override
    public int hashCode() {
        return internalUUID.hashCode();
    }

    @Override
    public boolean equals(Object o) {
        return (o == this || (o instanceof BaseEntity &&
internalUUID.equals(((BaseEntity)o).internalUUID));
    }
}

This solution is even better because primary key is an efficient numeric
type and you can completly hide internalUUID for inherited classes.

Whatever is your choice, you still have the ability to override those
methods to fit special purpose...
 
Re: Use UUID as primary key or opaque field 19 Jul 2006, 11:49 sma202
>This solution is even better because primary key is an efficient
numeric
>type and you can completly hide internalUUID for inherited classes.

>Whatever is your choice, you still have the ability to override those
>methods to fit special purpose...

How will this work for detached/reattached objects?  A random uuid 
will be generated even though the objects are the same database-wise.
 
Re: Use UUID as primary key or opaque field 03 Aug 2006, 07:18 fwolff
>How will this work for detached/reattached objects?  A random uuid
>will be generated even though the objects are the same database-wise.

The generated uuid is either saved in the database for new objects or
immediately overrided by its (previously saved) database value for
loaded objects. A detached object keeps its saved uuid (except if you
modify it explicitly) when reattached...
 
equals and hashCode another pattern proposal 22 Sep 2006, 11:10 nicolas_r
Hello,

Here is an implementation of equals and hashCode that  
passes successfully the 3 tests listed above in the article:
 - "multiple new instances in set"
 - "equal to same object from other session"
 - "collections intact after saving"

I did not test it against the "use in a composite-id" test.

The principle is simple: 
The first time equals or hashCode is called, we check if the 
primary key (here getUserId()) is present or not.
If yes: we use it in equals/hashcode
If no: we use a UID (here _uidInEquals) during the entire life of this
instance 
       even when latter on this instance is assigned a primary key.

I'd be happy to get some feedbacks on this implementation (is it
acceptable or not ?)
<code>
    private boolean _freezeUseUidInEquals = false;
    private boolean _useUidInEquals = true;
    private java.rmi.dgc.VMID _uidInEquals = null;

    private void setEqualsAndHashcodeStrategy() {
        if (_freezeUseUidInEquals == false) {
            _freezeUseUidInEquals = true;
            _useUidInEquals = (getUserId() == null);

            if (_useUidInEquals) {
                _uidInEquals = new java.rmi.dgc.VMID();
            }
        }
    }

	public boolean equals(Object object) {
		if (this == object) {
			return true;
        }

        if((object == null) || (object.getClass() != this.getClass()))  {
            return false;
        }

		UserModel other = (UserModel) object;

        setEqualsAndHashcodeStrategy();

        if (_useUidInEquals) {
            return _uidInEquals.equals(other._uidInEquals);
        } else {
            return getUserId().equals(other.getUserId());
        }
	}

	public int hashCode() {
        setEqualsAndHashcodeStrategy();

        if (_useUidInEquals) {
            return _uidInEquals.hashCode();            
        } else {
            return getUserId().hashCode();
        }
	}
</code>
 
Re: equals and hashCode another pattern proposal 01 Nov 2006, 10:03 simon_t
One issue with the implementation above is that the hashCode and equals
contract can be broken.

"If two objects are equal according to the equals(Object)  method, then
calling the hashCode method on each of the two objects must produce the
same integer result."

The consequence of this violation is demonstrated by the following
scenario.  Please note that the occurrence of this scenario may be quite
rare.

There is a User class and Role class.  The User class has a many-to-many
relationship with Role.

1. Create a new User in a Hibernate session.  Call it user1.
2. In another Hibernate session add a new Role to the user.  Call it role2.
3. Finally in a third hibernate session load the user from the database.
 Test that the role object created in step 2 is in user.roles using
Set.contains(Object)

The test in the third step should return true but instead returns false.
 The reason it returns false is because Set.contains(Object) uses
hashCode() before using equals().  equals() returns true when comparing
the Role created in step 2 to the Role loaded in step 3 but the
hashCodes are different.

Here is some sample code that demonstrates the scenario.  The comments
should explain what is going on.

    // save a User to the database
    Session session1 =
HibernateUtil.getSessionFactory().getCurrentSession();
    session1.beginTransaction();
    User user1 = new User();
    session1.save(user1);
    session1.getTransaction().commit();

    // in a new session add some Roles to the User
    // and check that all roles remain in User.roles
    Session session2 =
HibernateUtil.getSessionFactory().getCurrentSession();
    session2.beginTransaction();
    User user2 = (User) session2.load(User.class, user1.getId());
    Role role1 = new Role("role1");
    Role role2 = new Role("role2");
    user2.getRoles().add(role1);
    user2.getRoles().add(role2);
    session2.update(user2);
    session2.getTransaction().commit();
    System.out.println(user2.getRoles());
    // the statement above prints out "[role2, role1]"  This is correct
    
    // in another new session check that User.roles.contains()
    // works for a Role created above
    Session session3 =
HibernateUtil.getSessionFactory().getCurrentSession();
    session3.beginTransaction();
    User user3 = (User) session3.load(User.class, user1.getId());    
    System.out.println(user3.getRoles().contains(role2));
    // the statement above prints out false - it is meant to be true.
    // This fails because role2.hashCode() does not equals the hashCode
of the Role
    // loaded from the database
    for (Role role : user3.getRoles()) {
        if (role.equals(role2)) {
            System.out.println(role.hashCode() + "!=" + role2.hashCode());
        }
    }
    // The loop above prints out
    // "1!=-1901084812"
    // the hashCode of role2 does not equal the hashCode
    // of the equivalent Role in user3.getRoles() 
    session3.close();
    HibernateUtil.getSessionFactory().close();
 
Persist the hashCode, remove the need for UUIDs (single JVM app) 01 Nov 2006, 11:52 simon_t
I have a proposal for applications that run on a single JVM that was
inspired by the UUID solutions.  There are issues with this proposal for
applications spanning multiple JVMs (more on that later).

I may be pointing people in the wrong direction here so if you see
anything wrong with what is below please post a response.

I don't see why we have to implement equals using a UUID.  I can
understand why we have to for hashCode but not equals.

If an equals of a User object is implemented as:

    private Integer id;

    public void equal(Object object) {
        if (object == null) {
            return false;
        }
        if (!(object instanceof User)) {
            return false;
        }
        if (id == null) {
            return super.equals(object);
        } else {
            return id.equals(((User)object.id));
        }
    }
    
Then before a User is saved the id will be null.  It will not equal any
other object except itself.  After saving a User the id will not be null
and will equal any User object with the same id.

This equals will work for the following scenario:

1. A new User object is created, call it user1
2. In a Hibernate session user1 is saved.  At this point the user1
object will be assigned an id of 1.
3. In a different session the user with an id of 1 is loaded.  Call the
corresponding object user2
4. Using equals compare user1 to user2.  They will be equal because they
have the same id.

So what do we do with hashCode?  The issue with Hibernate and hashCodes
is that when an object is saved the hashCode changes.  A solution to
this is to derive the hashCode before saving and to persist this value
for future instances.

This can be implemented as (using java 1.5):

    private Integer hashCode;
    
    public int hashCode() {
        if (hashCode == null) {
            // provided the super classes do not override hashCode
            hashCode = super.hashCode();
        }
        return hashCode;
    }

    // expected to be mapped to a column in the database like HASH_CODE    
    public Integer getHashCode() {
        return hashCode;
    }

    // expected to be mapped to a column in the database like HASH_CODE
    public void setHashCode(Integer hashCode) {
        this.hashCode = hashCode;
    }
    
This implementation is similar to the UUID proposals (and in fact was
inspired by them) but will take up less space in the database.  Instead
of 128 bits only 32 bits are used.

So why will this not work on applications that span multiple JVMs?  This
is all theoretical because I've never worked on such an application but
I think my concerns are worth mentioning.  This proposal relies the fact
that when an instance of an object is saved, its id is set.  This does
not happen if an instance of an object is created in one JVM (call it
jvm1) and sent to a different JVM (call it jvm2) to be saved.  Hibernate
will only set the id of the instance in jvm2 not the id of the instance
in jvm1.  Setting of the id of the instance in jvm1 will not happen
without some extra mechanism to send the id back to jvm1.
 
Can't we store the hashcode in the database? 20 Apr 2007, 11:32 jonthe
I started out a big project using the approach recommended on this page
with an equals method based on business properties and a hashcode
corresponding to this. Yesterday my world crashed down upon me because
of strange errors. But hey I broke the hashcode/equals contract when I
changed properties of objects in a Set so I can only blame my self. 

Now the easiest solution I have come up with is the following:

add a field int hashcode to my objects and persist it with the object.
At creation time instatiate the field as a random number. In the equals
method check for both business property equality and hashcode equality.
(This is needed since the hascode is not an unique identifier of the
object). And the hascode()-method simply returns the stored hashcode.

Only downside I can figure out is that it is impossible to create two
equal objects other than by cloning, but that really shouldn't be a big
problem, and the saving of the hascode to the database but harddrives
are cheap and it ain't that big. Anyone see any other problems?
 
Re: fallback to object identiy 22 May 2007, 05:12 iwein
POST QUESTIONS ON THE FORUM! COMMENTS HERE SHOULD ADD VALUE TO THE
PAGE!On 26 Feb 2004 19:13, RobJellinghaus wrote:

>On 16 Feb 2004 16:02, hengels wrote:

>>what about using the primary key and fall back to object identity if a
>>pk is not assigned?

>This breaks collections because it changes the object's hashcode once
>you save it.  So you would get:

...

>This *will* print "OOPS!" because newUser's hashcode changed when you
>saved it, so the userSet can no longer find it.

It seems to me that you're assuming a 'wrong' implementation of
hashCode() based on the pk. since we established that the pk may change
(be set) in equal objects hasCode() is not allowed to change if the pk does.

Still you can use pk in equals(). Just compare the pk if and only if
both objects have pk!=0. In case of 0 compare the business key (i.e.
some properties). This is based on the assumption that you will never
change properties that undermine equality once you've saved an object
(set it's pk) i.e. objects that have the same pk are always equal based
on their business key.
 
Summed up 22 Jul 2007, 00:33 obsidian
The real problem, as others put it, is that the hibernate ID is the only
real candidate for equals()/hashCode(), but most people generate them in
the DB, so don't have it until they've saved, by which point it's
usually too late. Ge
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值