The JPA hashCode() / equals() dilemma The JPA hashCode() / equals() dilemma java java

The JPA hashCode() / equals() dilemma


Read this very nice article on the subject: Don't Let Hibernate Steal Your Identity.

The conclusion of the article goes like this:

Object identity is deceptively hard to implement correctly when objects are persisted to a database. However, the problems stem entirely from allowing objects to exist without an id before they are saved. We can solve these problems by taking the responsibility of assigning object IDs away from object-relational mapping frameworks such as Hibernate. Instead, object IDs can be assigned as soon as the object is instantiated. This makes object identity simple and error-free, and reduces the amount of code needed in the domain model.


I always override equals/hashcode and implement it based on the business id. Seems the most reasonable solution for me. See the following link.

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: enter image description here

EDIT:

To explain why this works for me:

  1. I don't usually use hashed-based collection (HashMap/HashSet) in my JPA application. If I must, I prefer to create UniqueList solution.
  2. I think changing business id on runtime is not a best practice for any database application. On rare cases where there is no other solution, I'd do special treatment like remove the element and put it back to the hashed-based collection.
  3. For my model, I set the business id on constructor and doesn't provide setters for it. I let JPA implementation to change the field instead of the property.
  4. UUID solution seems to be overkill. Why UUID if you have natural business id? I would after all set the uniqueness of the business id in the database. Why having THREE indexes for each table in the database then?


I personally already used all of these three stategies in different projects. And I must say that option 1 is in my opinion the most practicable in a real life app. In my experience breaking hashCode()/equals() conformity leads to many crazy bugs as you will every time end up in situations where the result of equality changes after an entity has been added to a collection.

But there are further options (also with their pros and cons):


a) hashCode/equals based on a set of immutable, not null, constructor assigned, fields

(+) all three criterias are guaranteed

(-) field values must be available to create a new instance

(-) complicates handling if you must change one of then


b) hashCode/equals based on a primary key that is assigned by the application (in the constructor) instead of JPA

(+) all three criterias are guaranteed

(-) you cannot take advantage of simple reliable ID generation stategies like DB sequences

(-) complicated if new entities are created in a distributed environment (client/server) or app server cluster


c) hashCode/equals based on a UUID assigned by the constructor of the entity

(+) all three criterias are guaranteed

(-) overhead of UUID generation

(-) may be a little risk that twice the same UUID is used, depending on algorythm used (may be detected by an unique index on DB)