package com.ankurm.hibernatedemo.bootstrap; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManagerFactory; import jakarta.persistence.Persistence; import jakarta.persistence.PersistenceConfiguration; import jakarta.persistence.PersistenceException; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Backs ankurm.com post 4855 (bootstrapping EntityManager). Docs: docs/17-entitymanager-bootstrap.md. * *

Deliberately plain JUnit, NOT {@code @SpringBootTest} -- the whole point of this chapter is * bootstrapping a raw JPA {@link EntityManagerFactory} with no Spring involved, exactly as a * Java SE application or a unit test outside a Spring context would. The XML path reads * {@code src/test/resources/META-INF/persistence.xml}; the programmatic path uses Jakarta * Persistence 3.2's {@link PersistenceConfiguration}, new in this version. * *

Run with {@code ./mvnw -Dtest=EntityManagerBootstrapTest test}. */ class EntityManagerBootstrapTest { private static final Logger DEMO = LoggerFactory.getLogger("DEMO"); @Test void xmlBootstrap_createsFactoryFromPersistenceXmlAndPersistsAUser() { // No properties map passed here at all -- everything comes from // META-INF/persistence.xml on the test classpath, resolved purely by unit name. try (EntityManagerFactory emf = Persistence.createEntityManagerFactory("XmlBootstrapPU")) { EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); BootstrapUser user = new BootstrapUser("Ankur", "admin@ankurm.com"); em.persist(user); em.getTransaction().commit(); Long id = user.getId(); em.close(); EntityManager reader = emf.createEntityManager(); BootstrapUser reloaded = reader.find(BootstrapUser.class, id); assertThat(reloaded).isNotNull(); assertThat(reloaded.getEmail()).isEqualTo("admin@ankurm.com"); reader.close(); DEMO.info("xmlBootstrap: persisted and reloaded user id={}", id); } } @Test void programmaticBootstrap_needsNoPersistenceXmlUnitOnClasspath() { // "ProgrammaticPU" has NO matching anywhere in persistence.xml -- // this only works at all if PersistenceConfiguration genuinely bypasses XML lookup. PersistenceConfiguration config = new PersistenceConfiguration("ProgrammaticPU") .provider("org.hibernate.jpa.HibernatePersistenceProvider") .managedClass(BootstrapUser.class) .property(PersistenceConfiguration.JDBC_DRIVER, "org.h2.Driver") .property(PersistenceConfiguration.JDBC_URL, "jdbc:h2:mem:bootstrap-programmatic;DB_CLOSE_DELAY=-1") .property(PersistenceConfiguration.JDBC_USER, "sa") .property(PersistenceConfiguration.JDBC_PASSWORD, "") .property("hibernate.hbm2ddl.auto", "create-drop"); try (EntityManagerFactory emf = config.createEntityManagerFactory()) { EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); BootstrapUser user = new BootstrapUser("Programmatic", "prog@ankurm.com"); em.persist(user); em.getTransaction().commit(); Long id = user.getId(); em.close(); EntityManager reader = emf.createEntityManager(); assertThat(reader.find(BootstrapUser.class, id)).isNotNull(); reader.close(); DEMO.info("programmaticBootstrap: persisted user id={} with zero persistence.xml units named 'ProgrammaticPU'", id); } } @Test void persistenceUnitName_inProgrammaticConfig_doesNotTriggerXmlSearch() { // Reusing "XmlBootstrapPU" -- the EXACT SAME name as the real XML unit -- but with // completely different properties built purely in code, pointed at a third database. // If the name alone triggered an XML lookup/merge, this factory would end up // connected to bootstrap-xml (the XML unit's database) instead. It doesn't. PersistenceConfiguration config = new PersistenceConfiguration("XmlBootstrapPU") .provider("org.hibernate.jpa.HibernatePersistenceProvider") .managedClass(BootstrapUser.class) .property(PersistenceConfiguration.JDBC_DRIVER, "org.h2.Driver") .property(PersistenceConfiguration.JDBC_URL, "jdbc:h2:mem:bootstrap-namecollision;DB_CLOSE_DELAY=-1") .property(PersistenceConfiguration.JDBC_USER, "sa") .property(PersistenceConfiguration.JDBC_PASSWORD, "") .property("hibernate.hbm2ddl.auto", "create-drop"); try (EntityManagerFactory emf = config.createEntityManagerFactory()) { EntityManager em = emf.createEntityManager(); @SuppressWarnings("unchecked") java.util.List row = em.createNativeQuery("SELECT DATABASE()").getResultList(); String actualDb = (String) row.get(0); assertThat(actualDb).as("the programmatic config's own JDBC URL won, not the XML unit's") .isEqualToIgnoringCase("BOOTSTRAP-NAMECOLLISION"); em.close(); DEMO.info("persistenceUnitNameCollision: connected database = {} (unit name 'XmlBootstrapPU' reused on purpose)", actualDb); } } @Test void unconfiguredUnitName_failsWithPersistenceException_notSilently() { // "TotallyUnknownPU" exists neither in persistence.xml nor as a PersistenceConfiguration // -- Persistence.createEntityManagerFactory(name) with no config map has nowhere left // to look. assertThatThrownBy(() -> Persistence.createEntityManagerFactory("TotallyUnknownPU")) .isInstanceOf(PersistenceException.class) .satisfies(ex -> { DEMO.info("unconfiguredUnitName: {}: {}", ex.getClass().getName(), ex.getMessage()); assertThat(ex.getMessage()).contains("TotallyUnknownPU"); }); } @Test void repeatedFactoryCreation_isMeasurablyExpensive() { // Not a Metaspace OOM reproduction -- that needs sustained, uncollectable class-loader // growth over many thousands of factories and isn't something to actually trigger in a // shared CI sandbox. What IS safely measurable: EntityManagerFactory creation is not // cheap, which is the whole reason "never create one per request" is the rule in the // first place. PersistenceConfiguration config = new PersistenceConfiguration("TimingPU") .provider("org.hibernate.jpa.HibernatePersistenceProvider") .managedClass(BootstrapUser.class) .property(PersistenceConfiguration.JDBC_DRIVER, "org.h2.Driver") .property(PersistenceConfiguration.JDBC_URL, "jdbc:h2:mem:bootstrap-timing;DB_CLOSE_DELAY=-1") .property(PersistenceConfiguration.JDBC_USER, "sa") .property(PersistenceConfiguration.JDBC_PASSWORD, "") .property("hibernate.hbm2ddl.auto", "create-drop"); long factoryStart = System.nanoTime(); EntityManagerFactory emf = config.createEntityManagerFactory(); long factoryMillis = (System.nanoTime() - factoryStart) / 1_000_000; long emStart = System.nanoTime(); EntityManager em = emf.createEntityManager(); long emMillis = (System.nanoTime() - emStart) / 1_000_000; em.close(); emf.close(); assertThat(factoryMillis).as("factory creation happened and was timed").isGreaterThanOrEqualTo(0); DEMO.info("repeatedFactoryCreation: createEntityManagerFactory() took {} ms, createEntityManager() took {} ms -- " + "the factory call is the one doing schema validation, service registry bootstrap, and metadata scanning; " + "the EntityManager call is comparatively trivial", factoryMillis, emMillis); } }