equals and hashCode for entities
Keep an entity usable in a HashSet both before and after Hibernate assigns its id.
Open this lesson in the learning hubKey points
- A generated id is null until flush. Deriving
hashCodefrom it means the hash changes while the object sits in a set. - Once the hash moves,
HashSetprobes the wrong bucket, socontainsis false for an object it still holds. - Best answer: use the natural business key - ISBN, email, order number - and mark it
@NaturalId. - With no natural key, return a constant
hashCodeand compare ids inequals. One bucket, but always correct. - Compare with
instanceof, notgetClass(): a lazy proxy is a subclass, so getClass rejects a valid match. - Records cannot be entities: they are final, have no no-arg constructor, and their generated
equalscovers every field.
Example
import java.util.HashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Set<Book> byId = new HashSet<>();
Book book = new Book("978-0134685991", "Effective Java");
byId.add(book);
System.out.println("id-based before flush : contains=" + byId.contains(book));
book.id = 42L; // exactly what flush does to a new entity
System.out.println("id-based after flush : contains=" + byId.contains(book));
System.out.println("id-based still stored : size=" + byId.size()
+ ", title=" + byId.iterator().next().title);
Set<Isbn> byKey = new HashSet<>();
Isbn stable = new Isbn("978-0134685991", "Effective Java");
byKey.add(stable);
System.out.println("key-based before flush : contains=" + byKey.contains(stable));
stable.id = 42L;
System.out.println("key-based after flush : contains=" + byKey.contains(stable));
System.out.println("key-based equal copy : contains="
+ byKey.contains(new Isbn("978-0134685991", "other title")));
}
/** The bug: the hash comes from a value that is null until the insert. */
static final class Book {
Long id;
final String isbn;
final String title;
Book(String isbn, String title) { this.isbn = isbn; this.title = title; }
@Override public int hashCode() { return id == null ? 0 : id.hashCode(); }
@Override public boolean equals(Object o) {
return o instanceof Book b && id != null && id.equals(b.id);
}
}
/** The fix: hash the natural key, which never changes. */
static final class Isbn {
Long id;
final String isbn;
final String title;
Isbn(String isbn, String title) { this.isbn = isbn; this.title = title; }
@Override public int hashCode() { return isbn.hashCode(); }
@Override public boolean equals(Object o) {
return o instanceof Isbn other && isbn.equals(other.isbn);
}
}
}
Never hash a value Hibernate is going to change for you.
This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Hibernate course, and every lesson in it is listed on the Hibernate contents page.