Add Hibernate 7 batches 2-6: mapping styles, JPA annotations, natural IDs, @Immutable, stored procedures, in-memory test databases, JNDI mocking, proxies, associations, temporal mapping, named queries, HQL, Criteria API and EntityManager bootstrapping (Hibernate 7.4.5.Final + Spring Boot 4.1.1 + JDK 25)
Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EQNA6DJ9VgCtW6zhCE8Xud
This commit is contained in:
+84
@@ -0,0 +1,84 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user