<a href="https://www.buymeacoffee.com/eduardoeljaiek" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-blue.png" alt="Buy Me A Coffee" style="height: 35px !important;width: 125px !important;" ></a> *** ### Why is it not recommended? Using `@Data` or `@EqualsAndHashCode` for JPA entities is not recommended. It can cause severe performance and memory consumption issues, such as: - Accidentally loading lazy attributes - Broken _HashSets_ and _HashMaps_ - Missing *No-Argument* constructor > [!NOTE] JPA Entities transient state considerations > Note that, newly created JPA entity objects that have no association with a [persistence context](https://www.baeldung.com/jpa-hibernate-persistence-context) are considered to be in the transient state. These objects usually do not have their _@Id_ members populated. Therefore, if *equals()* or *hashCode()* use the *id* in their calculations, this means all transient objects will be equal because their *id*s will all be null. There are not many cases where this is desirable. ### What should you do instead? Replace them with safe annotations: - `@ToString` can still be used, but all the lazy fields need to be excluded. This can be achieved by placing `@ToString.Exclude` on the desired fields, or by using `@ToString(onlyExplicitlyIncluded = true`) on the class and `@ToString.Include` on non-lazy fields. Then, override the default `Java.lang.Object` `equals()` and `hashCode()`. Additionally, I advise testing your `equals()` and `hashCode()` overriding, using [EqualsVerifier](https://jqno.nl/equalsverifier/) library. > [!NOTE] Why do you need equals() and hashCode() > > If you don’t implement `equals` and `hashCode` then the merge test will fail, therefore breaking the consistency guarantee. > > \~ Vlad Mihalcea **Option 1: Using a business key** - The `@NaturalId` annotation allows us to fetch the `Person` entity by its business key. > [!HINT] Optimizing the entity identifier retrieval > > You can skip the entity identifier retrieval by its associated natural key using the Hibernate [`@NaturalIdCache`](http://docs.jboss.org/hibernate/orm/current/javadocs/org/hibernate/annotations/NaturalIdCache.html): > > - Hibernate [second-level cache](https://vladmihalcea.com/jpa-hibernate-second-level-cache/) must be enabled. > - The `@Cache` annotation marks the [cache concurrency strategy](https://vladmihalcea.com/how-does-hibernate-read_write-cacheconcurrencystrategy-work/). > - The `@NaturalIdCache` tells Hibernate to cache the entity identifier associated with a given business key. ```java @Entity @Getter @Setter @ToString(onlyExplicitlyIncluded = true) class Person { @Id @Include private Long id; @Include @NaturalId(mutable = true) private String email; @Include private String firstName; @Include private String lastName; @ManyToOne(fetch = LAZY) private AddressEntity address; @Override public final boolean equals(Object obj) { if (this == obj) return true; return (obj instanceof Person other) && Objects.equals(getEmail(), other.getEmail()); } @Override public final int hashCode() { return Objects.hashCode(email); } } class PersonTests { @Test void shouldMeetEqualsAndHashCodeContract() { EqualsVerifier.forClass(Person.class) .suppress(ALL_FIELDS_SHOULD_BE_USED) .verify(); } } ``` **Option 2: Using a Database Key** - Normally, these entities have a primary key that is a unique value. Therefore, any instances of this entity that have the same primary key value are equal. - Mind that the `hashCode` implementation should always return the same value, which for entities is not really a problem since you don't fetch many entities per DB transaction. ```java @Entity @Getter @Setter @ToString class AddressEntity { @Id private Long id; private String street; private String zipCode; @Override public final boolean equals(Object obj) { if (this == obj) return true; return (obj instanceof Address other) && Objects.equals(getId(), other.getId()); } @Override public final int hashCode() { return getClass().hashCode(); } } class AddressEntityTests { @Test void shouldMeetEqualsAndHashCodeContract() { EqualsVerifier.forClass(Address.class) .suppress(ALL_FIELDS_SHOULD_BE_USED, STRICT_HASHCODE) .verify(); } } ``` > [!WARNING] Dealing with large sets of entities > The only time when you’ll see a performance bottleneck due to a single hash bucket is if you have a large collection of tens of thousands of entries. > >But then, it implies that you fetched that large collection from the database. The performance penalty of fetching such a collection from the database is multiple orders of magnitude higher than the single bucket overhead. > That’s why you never map large collections with Hibernate. You use queries for those instead. But then, for small collections.  > >Also, most of the time you don’t even need to use a `Set` or a `Map`. For bidirectional associations, `List(s)`perform better anyway. > >\~ Vlad Mihalcea *** **References**: - [JPA Entity Equality](https://www.baeldung.com/jpa-entity-equality) - [Lombok and JPA: What may go wrong?](https://jpa-buddy.com/blog/lombok-and-jpa-what-may-go-wrong/) - [The best way to map a @NaturalId business key with JPA and Hibernate](https://vladmihalcea.com/the-best-way-to-map-a-naturalid-business-key-with-jpa-and-hibernate/) - [The best way to implement equals, hashCode, and toString with JPA and Hibernate](https://vladmihalcea.com/the-best-way-to-implement-equals-hashcode-and-tostring-with-jpa-and-hibernate/) - [How to implement equals and hashCode using the entity identifier (primary key)](https://vladmihalcea.com/2016/06/06/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/)