132 lines
5.2 KiB
Java
Executable File
132 lines
5.2 KiB
Java
Executable File
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();
|
|
}
|
|
}
|