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:
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user