Add Hibernate 7 batches 2-6, batch 7, and batch 8: mapping styles, JPA annotations, natural IDs, @Immutable, stored procedures, in-memory test databases, JNDI mocking, proxies, associations, temporal mapping, named queries, HQL, Criteria API, EntityManager bootstrapping, Ehcache 3 L2 cache configuration, HikariCP connection pooling, Hibernate Validator CDI integration, aggregate functions, sorting, pagination, interceptors, and Hibernate Search 8 (Hibernate 7.4.5.Final + Spring Boot 4.1.1 + JDK 25)

This commit is contained in:
2026-09-20 06:06:42 +00:00
committed by Claude
commit 8568c0ce6c
330 changed files with 23668 additions and 0 deletions
@@ -0,0 +1,131 @@
package com.ankurm.hibernatedemo.proxy;
import static org.assertj.core.api.Assertions.assertThat;
import jakarta.persistence.EntityGraph;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import java.util.Map;
import org.hibernate.Hibernate;
import org.hibernate.SessionFactory;
import org.hibernate.stat.Statistics;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* Backs docs/11-proxies-and-lazy-initialization.md, chapter "fetchgraph vs loadgraph, actually demonstrated".
*
* <p>{@link ProxyBook} has two associations: {@code publisher} (default fetch type, EAGER) and
* {@code reviews} (explicit LAZY). The named graph {@code Book.reviews-only} names only
* {@code reviews}. The two JPA hint keys treat the association NOT named in the graph
* differently:
*
* <ul>
* <li>{@code jakarta.persistence.loadgraph} -- attributes not in the graph keep their
* mapped fetch type. {@code publisher} stays EAGER and gets joined anyway.</li>
* <li>{@code jakarta.persistence.fetchgraph} -- the graph IS the complete fetch plan.
* Any attribute not in it is forced to LAZY, so {@code publisher} does NOT get joined,
* regardless of its mapped EAGER fetch type.</li>
* </ul>
*/
@SpringBootTest
class EntityGraphFetchTest {
private static final Logger DEMO = LoggerFactory.getLogger("DEMO");
@Autowired
private EntityManagerFactory emf;
private Statistics stats() {
return emf.unwrap(SessionFactory.class).getStatistics();
}
private Long seedBookWithReviewAndPublisher() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
ProxyPublisher publisher = new ProxyPublisher("Graph Press");
em.persist(publisher);
ProxyBook book = new ProxyBook("Graph Book", publisher);
em.persist(book);
ProxyReview review = new ProxyReview("Nice graph", book);
em.persist(review);
em.getTransaction().commit();
Long id = book.getId();
em.close();
return id;
}
@Test
void loadgraph_joinsNamedAttributeAndKeepsMappedEagerAttributeJoinedToo() {
Long id = seedBookWithReviewAndPublisher();
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
EntityGraph<?> graph = em.getEntityGraph("Book.reviews-only");
stats().clear();
ProxyBook book = em.find(ProxyBook.class, id, Map.of("jakarta.persistence.loadgraph", graph));
// One SELECT total: publisher (mapped EAGER, loadgraph does not touch it) and
// reviews (named in the graph) are both fetched without a second round-trip.
assertThat(stats().getPrepareStatementCount())
.as("loadgraph must fetch the named attribute AND keep the mapped-EAGER one, in one query")
.isEqualTo(1);
assertThat(Hibernate.isInitialized(book.getPublisher()))
.as("loadgraph does not demote attributes outside the graph -- publisher is still EAGER")
.isTrue();
assertThat(Hibernate.isInitialized(book.getReviews())).isTrue();
em.getTransaction().commit();
em.close();
}
@Test
void fetchgraph_joinsOnlyNamedAttributeAndForcesMappedEagerAttributeToLazy() {
Long id = seedBookWithReviewAndPublisher();
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
EntityGraph<?> graph = em.getEntityGraph("Book.reviews-only");
stats().clear();
ProxyBook book = em.find(ProxyBook.class, id, Map.of("jakarta.persistence.fetchgraph", graph));
assertThat(stats().getPrepareStatementCount())
.as("fetchgraph's single SELECT still includes the join for the named attribute")
.isEqualTo(1);
assertThat(Hibernate.isInitialized(book.getReviews())).isTrue();
assertThat(Hibernate.isInitialized(book.getPublisher()))
.as("fetchgraph treats the graph as the WHOLE fetch plan -- publisher is forced back to LAZY "
+ "even though its mapping says EAGER")
.isFalse();
DEMO.info("fetchgraph: book.getPublisher() runtime class = {}", book.getPublisher().getClass().getName());
em.getTransaction().commit();
em.close();
}
@Test
void noGraphAtAll_defaultFindStillJoinsTheMappedEagerAssociationButLeavesLazyCollectionAlone() {
Long id = seedBookWithReviewAndPublisher();
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
stats().clear();
ProxyBook book = em.find(ProxyBook.class, id);
assertThat(stats().getPrepareStatementCount()).isEqualTo(1);
assertThat(Hibernate.isInitialized(book.getPublisher()))
.as("plain find() honours the mapping: publisher is EAGER by default")
.isTrue();
assertThat(Hibernate.isInitialized(book.getReviews()))
.as("plain find() honours the mapping: reviews is LAZY by default")
.isFalse();
em.getTransaction().commit();
em.close();
}
}