equals() and hashCode() in Hibernate: Five Patterns That Look Right and Aren’t
A team added a Product to a HashSet, called em.persist(product), and then checked whether the set contained it. The answer was false. The entity was in the database. It was in the set before the persist call. After the call it had effectively vanished from the set, even though nobody removed it. The bug is the database-generated ID in hashCode(). Before persist, id is null; hashCode() returns some value based on null. After persist, id is 42; hashCode() returns a completely different value. The entity is now sitting in the wrong bucket of the HashSet. contains() looks in the right bucket, finds nothing, returns false. The entity is lost — not from the database, just from the set — and every operation that relied on that set produces wrong answers. This is pattern one. There are four more, each equally invisible until the wrong moment. Each one looks like a reasonable implementation when you read it. The problem only shows up at runtime, often under specific conditions that don't reproduce in unit tests.