Java › equals(), hashCode() & toString() Contracts
Why is it particularly important for a class's equals() and hashCode() to be based on effectively immutable data specifically when that class is used as a JPA/Hibernate entity, and what special considerations apply to entity identity before it's persisted (has a generated ID)?
IntermediateAnswer
JPA entities present a unique challenge for equals()/hashCode(): using the auto-generated database ID (common and seemingly natural) is problematic because a newly-created, not-yet-persisted entity has a null ID (making two distinct new entities awkwardly 'equal' if ID-based equals() treats null IDs as equal, or requiring special-casing), and worse, an entity's hashCode() based on its ID would change once the ID gets assigned during the persist operation, breaking the immutable-hashCode expectation of hash-based collections if the entity was added to a Set before being saved -- common approaches include using a separate, always-present business/natural key for equality (if one genuinely exists), or accepting the added complexity of correctly handling the transient (unsaved) versus persisted entity states explicitly.
public class Order {
private Long id; // null until persisted!
private String orderNumber; // business key, present immediately, before persistence
@Override
public boolean equals(Object o) {
if (!(o instanceof Order other)) return false;
return orderNumber != null && orderNumber.equals(other.orderNumber); // use stable business key, not the DB-generated id
}
@Override
public int hashCode() { return orderNumber != null ? orderNumber.hashCode() : System.identityHashCode(this); }
}Real-world example A team's JPA entity originally used the auto-generated database ID for equals()/hashCode() and experienced entities silently 'disappearing' from a HashSet after being persisted (since the ID, and therefore the hashCode, changed after being assigned by the database), resolved by switching to a stable business key present even before the entity is saved.