Hibernate Entity Equals Policy
ID |
java.hibernate_entity_equals_policy |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
Java |
Tags |
hibernate, orm, reliability |
Description
Reports @Entity classes whose equals(Object) or hashCode() methods reference the @Id field. The JPA identifier is null for unsaved entities and changes after persist(), breaking hash-based collections that hold entities across lifecycle boundaries.
Rationale
Hibernate entities go through a lifecycle: transient (no id), managed (assigned id), detached. If equals or hashCode depend on the @Id field, the entity’s hash changes when persisted, causing it to "disappear" from HashSet or HashMap:
@Entity
public class Order {
@Id @GeneratedValue
private Long id;
@Override
public boolean equals(Object o) {
if (!(o instanceof Order)) return false;
return Objects.equals(id, ((Order) o).id); // Bug: id is null before persist
}
@Override
public int hashCode() {
return Objects.hash(id); // Bug: hash changes after persist
}
}
Adding a transient Order to a Set, then persisting it, makes contains() return false for the same object because the hash changed.
Remediation
Use a stable, immutable business-key field (e.g., email, UUID, natural key) or omit equals/hashCode entirely (inherit Object identity):
@Entity
public class Order {
@Id @GeneratedValue
private Long id;
private String orderNumber; // stable business key
@Override
public boolean equals(Object o) {
if (!(o instanceof Order)) return false;
return Objects.equals(orderNumber, ((Order) o).orderNumber);
}
@Override
public int hashCode() {
return Objects.hash(orderNumber);
}
}