gerrypower wrote:
I am definitely no expert here, but I stand aghast that no one has addressed this critical issue by summarizing the key real life issues, and the Hibernate issues and implications to consider for each approach. 109.html doesn't do it, nor does this thread, nor does the documentation.
So I ask the community at large, is there a Hibernate expert out there that can address this critical issue for us?
Gerry's right.
One looks for a fully general, safe solution to these things.
I'll describe how I see it. If that's wrong, hopefully it'll annoy someone smarter than me into correcting me.
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. General code doesn't like distinguishing between created vs. loaded objects. Wanting to mix the two types in a collection, including one that can survive across Sessions, is quite an ordinary, even basic desire IMO.
It seems like the official advice is to fake out equals()/hashCode() with data that's highly unlikely to meet the contract of those methods. Some people are lucky and have "business key" fields that change less often than their collections live for. Most people aren't lucky. Or if they are today, who can guarantee what someone may change tomorrow? The classic example of a business key is "name" or a "username" or whatever, yet names and usernames change all the time (i.e. it's an editor and the name/username's not immutable), and that makes it a fundamentally wrong thing to build equals/hashcode on. Using more mutable properties at once makes this problem worse, not better. If you've got a property that really doesn't ever change, why did you bother making a separate ID column anyway?
Don't get me wrong. I can see why this "business key" approach would work for some people, sometimes. I would simply caution against it far more strongly than the hibernate documentation does. Let's call it what it is. It's usually going to be a risky shortcut. And when you lose at identifer roulette, you will not always get a pretty error message to tell you.
I'd have laid out the solutions differently:
1) Have your equals/hashCode methods work off of IDs, and use an ID generation strategy that allows you to know the ID when the object is created.
1a) UUID, or something more exotic. Everyone hates these. UUIDs are too big, and annoying if you share your tables with non-hibernate apps. They're also just plain uncommon, which makes them seem spooky to most people.
1b) Keep your sequences, and use an API, rather than new, to create your entities, have it hit the DB to get the identifier in advance (i.e. run the sequence). Nobody seems to like this either, because you pay a (by comparison to the normal case) staggering IO cost to instantiate an object.
See also things like:
http://www.devx.com/Java/Article/30396
2) Try this dangerous fakery with "business keys." I hope you have some, and that you don't change them, nor does anyone else who comes along later doing things you didn't anticipate today.
3) Get scared away and try not to need equals()/hashCode() after all. Hibernate doesn't, according to Gavin. (Nice work, by the way.)
I'll say this, it seems clear this is not a Hibernate problem, except inasmuch as Hibernate makes your life so easy it can obscure certain immutable underlying facts about life with an RDBMS. Any system managing sometimes-new/sometimes-persistent objects will have to find an answer for this.