Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EQNA6DJ9VgCtW6zhCE8Xud
85 lines
4.0 KiB
Java
Executable File
85 lines
4.0 KiB
Java
Executable File
package com.ankurm.hibernatedemo.naturalid;
|
|
|
|
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;
|
|
|
|
/**
|
|
* Does the "base equals()/hashCode() on the natural id, not the surrogate id" advice from
|
|
* posts 4864/4865 actually hold up? Reproduces the SAME HashSet-before-flush scenario as
|
|
* {@code IdBasedEqualsHashSetTrapTest} (persistenceannotations package) but with natural-id-based
|
|
* equals/hashCode instead of surrogate-id-based -- this time contains() should stay TRUE.
|
|
*
|
|
* <p>Docs: docs/06-natural-ids.md, Topic 3.
|
|
*/
|
|
@SpringBootTest(classes = HibernateDemoApplication.class)
|
|
class NaturalIdEqualsHashSetTest {
|
|
|
|
@Autowired
|
|
private EntityManagerFactory emf;
|
|
|
|
@Test
|
|
void naturalIdBasedEquals_addedToSetBeforeFlush_containsStaysTrueAfterPersist() {
|
|
Set<NaturalIdEqualsEntity> set = new HashSet<>();
|
|
NaturalIdEqualsEntity e = new NaturalIdEqualsEntity("SKU-EQ-1", "widget");
|
|
|
|
set.add(e); // sku is already set -- hashCode() is stable from construction
|
|
assertThat(set.contains(e)).isTrue();
|
|
|
|
EntityManager em = emf.createEntityManager();
|
|
em.getTransaction().begin();
|
|
em.persist(e);
|
|
em.getTransaction().commit(); // id gets assigned, but sku (and hence hashCode) never changes
|
|
em.close();
|
|
|
|
boolean stillFound = set.contains(e);
|
|
System.out.println("RESULT[naturalid-equals-hashset]: after persist(), e.getId()=" + e.getId()
|
|
+ ", e.getSku()=" + e.getSku() + ", set.contains(e) = " + stillFound
|
|
+ " (equals/hashCode based on the immutable natural id, NOT the surrogate id)");
|
|
|
|
assertThat(stillFound)
|
|
.as("natural-id-based equals/hashCode must survive persist() -- the advice holds up, unlike the surrogate-id version")
|
|
.isTrue();
|
|
}
|
|
|
|
@Test
|
|
void naturalIdBasedEquals_twoTransientInstancesWithDifferentSkus_areNotEqual() {
|
|
NaturalIdEqualsEntity a = new NaturalIdEqualsEntity("SKU-EQ-A", "Widget A");
|
|
NaturalIdEqualsEntity b = new NaturalIdEqualsEntity("SKU-EQ-B", "Widget B");
|
|
|
|
// Unlike surrogate-id equals (where two transient instances are BOTH "equal" because
|
|
// both ids are null), natural-id equals correctly distinguishes them even before persist.
|
|
System.out.println("RESULT[naturalid-equals-transient]: a.equals(b) for two DIFFERENT transient instances = "
|
|
+ a.equals(b) + " (both have null surrogate ids, but different natural ids)");
|
|
assertThat(a).isNotEqualTo(b);
|
|
}
|
|
|
|
@Test
|
|
void naturalIdBasedEquals_twoTransientInstancesWithSameSku_areEqual_evenBeforePersist() {
|
|
// This is the flip side worth knowing: TWO DIFFERENT transient objects representing
|
|
// the "same" business entity (same sku, not yet persisted) are already equal() to
|
|
// each other, well before either has a database identity. That is usually desirable,
|
|
// but it means a HashSet can silently dedupe two not-yet-persisted objects.
|
|
NaturalIdEqualsEntity a = new NaturalIdEqualsEntity("SKU-EQ-DUP", "First instance");
|
|
NaturalIdEqualsEntity b = new NaturalIdEqualsEntity("SKU-EQ-DUP", "Second instance, different name");
|
|
|
|
Set<NaturalIdEqualsEntity> set = new HashSet<>();
|
|
set.add(a);
|
|
boolean bWasRejectedAsADuplicate = !set.add(b);
|
|
|
|
System.out.println("RESULT[naturalid-equals-transient-dup]: a.equals(b)=" + a.equals(b)
|
|
+ " for two transient instances sharing sku='SKU-EQ-DUP' but different names; "
|
|
+ "HashSet.add(b) rejected it as a duplicate = " + bWasRejectedAsADuplicate);
|
|
|
|
assertThat(a).isEqualTo(b);
|
|
assertThat(bWasRejectedAsADuplicate).isTrue();
|
|
}
|
|
}
|