Some of the problems in everyday software development are luxury problems. Like when you work in a greenfield project and are able to choose how you implement equals() and hashCode(). However, this decision will have a major effect for the project down the road. So here are the options I found out and my decision making process.
In the excellent article How to implement Equals and HashCode for JPA entities, Vlad Mihalcea summed up the options before deciding that business keys are the best way to go. My first instinct, using database IDs, is apparently a bad idea. A freshly created object that doesn’t have an ID yet would be different from an object that is loaded from the database, even if they represent the same thing. Hence, Vlad suggests using a business key which is a “combination of fields that’s unique among Entities”. This “business key must be set from the very moment we are creating the Entity and then never change it.” As an example, he shows a Company- class in which the name of this company is a never-changing property. To be honest, I think this is a bad example because we’ve all seen things changing although the customer swore “This will never ever change!” I think the name of a company is very likely to change sometimes, even for big companies like Krupp which is now ThyssenKrupp. However, I do understand the need for such a never-changing attribute.
Vlad suggests the following implementation:
If this field will never change, I think it would be wise to have a constructor with all those non-updatable fields so it’s not possible to construct an object without them. Theoretically, there shouldn’t be setter for those fields. However, frameworks like Spring Data JPA need the default constructor and getter/setter to handle the bean correctly.
This article provides a nice decision matrix which supports using business keys for equals() and hashCode().
To avoid the famous LazyInitializationException, references that are annotated with FetchType.LAZY should not be used in equals() and hashCode().
Also, I found a little update for using the EqualsBuilder and HashCodeBuilder so the resulting methods look like this:
In my situation it’s very easy to change my small existing codebase to Vlads suggestion. However, I will have to write tests to really believe that this change doesn’t break anything.
Use a combination of business keys for equals() and hashCode().