package com.ankurm.hibernatedemo.persistenceannotations; import static org.assertj.core.api.Assertions.assertThat; import com.ankurm.hibernatedemo.HibernateDemoApplication; import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManagerFactory; import java.util.HashSet; import java.util.Set; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; /** * The concrete failure: an entity with id-based equals()/hashCode() is added to a HashSet * BEFORE flush (id == null), then persisted (id gets assigned). The same reference, looked up * in the SAME set, is no longer found -- because its hash code changed after insertion, so * HashSet is now probing the wrong bucket. * *

Docs: docs/05-jpa-persistence-annotations.md, Topic 2. */ @SpringBootTest(classes = HibernateDemoApplication.class) class IdBasedEqualsHashSetTrapTest { @Autowired private EntityManagerFactory emf; @Test void idBasedEquals_addedToSetBeforeFlush_containsReturnsFalseAfterIdAssigned() { Set set = new HashSet<>(); IdBasedEqualsEntity e = new IdBasedEqualsEntity("widget"); set.add(e); // id is null at this point -- hashCode() = Objects.hash((Long) null) assertThat(set.contains(e)).isTrue(); // trivially true right now EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); em.persist(e); em.getTransaction().commit(); // id gets assigned HERE -- hashCode() changes em.close(); assertThat(e.getId()).isNotNull(); boolean stillFound = set.contains(e); System.out.println("RESULT[id-based-equals-hashset-trap]: after persist(), e.getId()=" + e.getId() + ", set.contains(e) = " + stillFound + " (same reference, same set, only the hash code changed)"); assertThat(stillFound) .as("HashSet.contains() on the SAME reference must return false once the id-based hashCode changed after insertion") .isFalse(); // Bonus: iterating the set and calling equals() manually still finds it (equals() itself is fine) -- // it's specifically the bucket lookup (which trusts the now-stale cached hash) that is broken. boolean foundByIteration = set.stream().anyMatch(x -> x.equals(e)); System.out.println("RESULT[id-based-equals-hashset-trap]: manual iteration foundByIteration = " + foundByIteration + " -- confirms equals() itself still works; it's HashSet's bucket indexing that is now wrong."); assertThat(foundByIteration).isTrue(); } }