<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/)