package com.ankurm.hibernatedemo.scenario; import com.ankurm.hibernatedemo.model.Book; import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManagerFactory; import org.hibernate.Session; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.CommandLineRunner; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; /** * Backs ankurm.com post 4859 ("Hibernate 7: get() vs load()") and * docs/01-get-vs-load.md. Captured verbatim into docs/output/get-vs-load.txt by * {@code scripts/run.sh getvsload}. * *

Each step opens its own {@link EntityManager} deliberately, so the SQL log lines that * bracket a step are unambiguously that step's own traffic — there is no shared session * whose first-level cache could quietly answer a later {@code get()} for free. */ @Component @Profile("getvsload") public class GetVsLoadRunner implements CommandLineRunner { private static final Logger DEMO = LoggerFactory.getLogger("DEMO"); private final EntityManagerFactory emf; public GetVsLoadRunner(EntityManagerFactory emf) { this.emf = emf; } @Override public void run(String... args) { Long existingId = seedOneBook(); long missingId = existingId + 999_000L; step1_getExisting(existingId); step2_getMissing(missingId); step3_getReferenceExisting_noSelectUntilAccessed(existingId); step4_getReferenceMissing_exceptionOnlyOnAccess(missingId); step5_getReferenceThenSessionClosed_lazyInitException(existingId); step6_proxyVsRealIdentity(existingId); } private Long seedOneBook() { EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); Book book = new Book("Effective Java", "Joshua Bloch"); em.persist(book); em.getTransaction().commit(); Long id = book.getId(); em.close(); DEMO.info("SEED: inserted Book id={}", id); return id; } private void step1_getExisting(Long id) { DEMO.info("--- Step 1: session.get() on an existing id ---"); EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); Session session = em.unwrap(Session.class); DEMO.info("about to call session.get(Book.class, {})", id); Book book = session.get(Book.class, id); DEMO.info("get() returned: {}", book); em.getTransaction().commit(); em.close(); } private void step2_getMissing(long missingId) { DEMO.info("--- Step 2: session.get() on a missing id ---"); EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); Session session = em.unwrap(Session.class); DEMO.info("about to call session.get(Book.class, {})", missingId); Book book = session.get(Book.class, missingId); DEMO.info("get() returned: {} (no exception thrown)", book); em.getTransaction().commit(); em.close(); } private void step3_getReferenceExisting_noSelectUntilAccessed(Long id) { DEMO.info("--- Step 3: session.getReference() on an existing id ---"); EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); Session session = em.unwrap(Session.class); Book proxy = session.getReference(Book.class, id); DEMO.info("getReference() returned proxy of class {} -- no SELECT above this line", proxy.getClass().getName()); DEMO.info("now calling proxy.getTitle() ..."); String title = proxy.getTitle(); DEMO.info("getTitle() returned '{}' -- the SELECT for this ran just above this line", title); em.getTransaction().commit(); em.close(); } private void step4_getReferenceMissing_exceptionOnlyOnAccess(long missingId) { DEMO.info("--- Step 4: session.getReference() on a missing id ---"); EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); Session session = em.unwrap(Session.class); Book proxy = session.getReference(Book.class, missingId); DEMO.info("getReference() returned a proxy for a row that does not exist -- no exception yet: {}", proxy.getClass().getName()); try { proxy.getTitle(); DEMO.info("no exception -- this line should be unreachable"); } catch (RuntimeException e) { DEMO.info("accessing the proxy threw {}: {}", e.getClass().getName(), e.getMessage()); } em.getTransaction().rollback(); em.close(); } private void step5_getReferenceThenSessionClosed_lazyInitException(Long id) { DEMO.info("--- Step 5: proxy accessed after its session is closed ---"); EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); Session session = em.unwrap(Session.class); Book proxy = session.getReference(Book.class, id); em.getTransaction().commit(); em.close(); DEMO.info("session closed. proxy in hand: {}", proxy.getClass().getName()); try { proxy.getTitle(); DEMO.info("no exception -- this line should be unreachable"); } catch (RuntimeException e) { DEMO.info("accessing the proxy after close threw {}: {}", e.getClass().getName(), e.getMessage()); } } private void step6_proxyVsRealIdentity(Long id) { DEMO.info("--- Step 6: proxy identity vs a real loaded instance ---"); EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); Session session = em.unwrap(Session.class); Book real = session.get(Book.class, id); // second em/session so this is a genuinely separate proxy, not the same cached instance EntityManager em2 = emf.createEntityManager(); em2.getTransaction().begin(); Session session2 = em2.unwrap(Session.class); Book proxy = session2.getReference(Book.class, id); DEMO.info("real.getClass() = {}", real.getClass().getName()); DEMO.info("proxy.getClass() = {}", proxy.getClass().getName()); DEMO.info("proxy instanceof Book.class: {}", Book.class.isInstance(proxy)); DEMO.info("real.getClass() == proxy.getClass(): {}", real.getClass() == proxy.getClass()); DEMO.info("real.equals(proxy) before proxy access: {}", real.equals(proxy)); em.getTransaction().commit(); em.close(); em2.getTransaction().commit(); em2.close(); } }