Yêu cầu cốt lõi: một entity phải bằng chính nó qua mọi trạng thái transient → managed → detached, và hashCode phải không đổi trong suốt vòng đời object.
Vì sao dùng thẳng ID auto-increment là sai: lúc new entity, id còn null nên hashCode ra một giá trị; sau persist, DB gán id nên hashCode đổi. Nếu entity đã nằm trong HashSet (rất hay gặp với @OneToMany Set<...>), nó rơi vào bucket khác và contains() trả về false cho chính object vừa thêm vào.
Hai cách được chấp nhận:
1. Business key — dùng field định danh nghiệp vụ, bất biến, gán ngay lúc tạo object (orderCode, sku, email, hoặc một UUID tự sinh ở tầng ứng dụng). Đây là cách sạch nhất.
2. ID với hashCode hằng số — so sánh theo id trong equals, nhưng hashCode trả về hằng:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Order other)) return false;
return id != null && id.equals(other.getId());
}
@Override
public int hashCode() {
return getClass().hashCode(); // stable across all entity states
}Tránh: để Lombok @Data/@EqualsAndHashCode sinh mặc định — nó gộp cả association lazy vào, gây truy vấn ngoài ý muốn hoặc đệ quy vô hạn giữa hai chiều quan hệ.