Lovely equals and hashcode, all the theory is here and also here
I have taken the decision to use the auto-generated id within equals() and hashcode() in a number of my hibernate entity/domain objects.
However, a number of websites say you should never do this due to the risk of persisting an object to the database for the first time whilst it is in the process of being compared or using hashcode.
My point of view is that in most use cases this is much more unlikely than any other field being changed.
The individual domain objects have the id generated once when they are first created, whereas nearly every other field has the opportunity to be altered during normal business processes (even a unique username can be changed ... ).
And in many of my domain object开发者_JAVA百科s the unique id is pretty much the only suitable field to be considered (Person, Address, Pet, ... Customer etc etc ? Combining fields is a good idea, but never using the auto generated id is, I think, not good advice.
Am I missing something else ?
You should read Equals and HashCode on the Hibernate Community Wiki.
The main reason for not using the database identifier in equals
, and by implication, hashCode
is for dealing with stored, but not persisted, entities. Prior to persistence all you instances are going to be equal
, unless you take care to handle that case explicitly.
If you know that you're not going to be in this scenario, and you make sure it's well documented, you may well be OK. You can always change the implementations later.
No, I don't think you're missing anything fundamental. The "root cause" of the problem with using the database ID for equals and hashCode is that a Hibernate-generated ID is stored in a mutable field, and the value of that field changes when Hibernate assigns the ID. Equals and hashCode should be based on immutable state, as described as "pitfall #3" in the Odersky / Spoon / Venners article on writing equals / hashCode methods. This means, among other things, that you cannot add an instance to a Set or compare it to another instance until after it has been persisted, when the hashCode becomes fixed.
The only thing you might be missing is how tricky it is for you to keep track of when the ID gets assigned, since it's done automatically by Hibernate. Sure, you might never call setId(someNewId)
, but you might issue a query that triggers a session flush, and a whole bunch of transient entities unrelated to the query suddenly have their IDs change from null to not-null.
Using a business key for equals and hashCode is often recommended by the Hibernate community, but this approach also suffers from the same mutable-until-initialized problem. If your entity is a member of a persistent set which is eager-loaded by Hibernate, then it can be added to a set before its fields are initialized, so a hashCode based on these fields does not work either. See this Hibernate bug report discussing the problem with business key equality.
James Brundege recommends assigning the ID in application code to avoid this problem. Lance Arlaus has a similar approach using an object factory which assigns IDs.
If you really want to use an auto-generated ID for equals and hashCode, consider using Assert.notNull(id)
in your implementations of equals and hashCode to detect coding errors.
See also another stackoverflow question with a lot of discussion on the different approaches.
I came across the equals problem with auto-id-generation because of the fact that I've put my objects into Set once they were created based on new user input. So I did not explictily compare them or something, i've just put them into a Set. At that point it was already fail, because the Set relies on equals()/hashCode() (which again were based on the object id because I had no other option).
I then changed it to actually generating and assigning id's myself, which was the far better solution.
Please read this article, it explains exactly the problem: dont-let-hibernate-steal-your-identity
精彩评论