Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EQNA6DJ9VgCtW6zhCE8Xud
60 lines
2.6 KiB
Java
Executable File
60 lines
2.6 KiB
Java
Executable File
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.
|
|
*
|
|
* <p>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<IdBasedEqualsEntity> 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();
|
|
}
|
|
}
|