# 17 — Bootstrapping EntityManager: XML, PersistenceConfiguration, and what a factory costs [← Previous: 16 — Criteria API](16-criteria-queries.md) | [Back to README →](../README.md) Backs ankurm.com post 4855 (bootstrapping EntityManager). Everything below comes from [`EntityManagerBootstrapTest`](../src/test/java/com/ankurm/hibernatedemo/bootstrap/EntityManagerBootstrapTest.java) -- deliberately **plain JUnit, not `@SpringBootTest`**. The whole point of this chapter is bootstrapping a raw JPA `EntityManagerFactory` with no Spring involved, the way a Java SE application, a batch job, or a unit test outside a Spring context would. Every other chapter in this repo runs inside Spring Boot's auto-configured persistence; this one deliberately doesn't. ## Two ways to bootstrap, both exercised for real **XML**: a [`persistence.xml`](../src/test/resources/META-INF/persistence.xml) on the classpath at the conventional `META-INF/persistence.xml` location, resolved purely by unit name: ```java 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(); // ... } ``` ``` xmlBootstrap: persisted and reloaded user id=1 ``` **Programmatic**, via Jakarta Persistence 3.2's `PersistenceConfiguration` -- new in this spec version, confirmed present via `javap` against `jakarta.persistence-api-3.2.0.jar` (constants like `JDBC_URL`, `JDBC_DRIVER`, `JDBC_USER`, `JDBC_PASSWORD` map to the same string property keys as the XML form's `` elements -- there's no dedicated `.jdbcUrl(String)` builder method, connection details go through `.property(PersistenceConfiguration.JDBC_URL, ...)`): ```java 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()) { // ... } ``` ``` programmaticBootstrap: persisted user id=1 with zero persistence.xml units named 'ProgrammaticPU' ``` That second result is the point of the test's name: `"ProgrammaticPU"` has **no** matching `` anywhere in `persistence.xml`. This only works at all if `PersistenceConfiguration` genuinely builds a persistence unit from code, with zero XML lookup. Source: [`EntityManagerBootstrapTest`](../src/test/java/com/ankurm/hibernatedemo/bootstrap/EntityManagerBootstrapTest.java), [`persistence.xml`](../src/test/resources/META-INF/persistence.xml). Raw output: [`docs/output/bootstrap-persistenceconfiguration.txt`](output/bootstrap-persistenceconfiguration.txt). Going deeper: - [`jakarta.persistence.PersistenceConfiguration` javadoc](https://jakarta.ee/specifications/persistence/3.2/apidocs/jakarta.persistence/jakarta/persistence/persistenceconfiguration) (`rel="nofollow"`) ## Reusing a persistence-unit name does not trigger an XML lookup — proven two ways This is the claim most worth being skeptical of, so it's verified twice, independently. The setup: build a `PersistenceConfiguration` with the name `"XmlBootstrapPU"` -- **the exact same name** as the real XML-defined unit -- but with completely different properties, pointed at a third H2 database (`bootstrap-namecollision`) built purely in code: ```java PersistenceConfiguration config = new PersistenceConfiguration("XmlBootstrapPU") .provider("org.hibernate.jpa.HibernatePersistenceProvider") .managedClass(BootstrapUser.class) .property(PersistenceConfiguration.JDBC_URL, "jdbc:h2:mem:bootstrap-namecollision;DB_CLOSE_DELAY=-1") // ... ``` **First proof** -- a native query asking the database itself which database it is: ```java List row = em.createNativeQuery("SELECT DATABASE()").getResultList(); String actualDb = (String) row.get(0); ``` ``` persistenceUnitNameCollision: connected database = BOOTSTRAP-NAMECOLLISION (unit name 'XmlBootstrapPU' reused on purpose) ``` If the reused name had triggered any XML lookup or merge, the factory would be connected to `bootstrap-xml` (the real XML unit's database) instead. It isn't. **Second proof** -- Hibernate's own bootstrap log, for the *same test run*, showing two completely different `PersistenceUnitInfo` entries logged under the identical name at two different points in the run: once for the programmatic config above, and once later when `xmlBootstrap_createsFactoryFromPersistenceXmlAndPersistsAUser` runs and actually does load the real XML unit: ``` 23:40:17.710 [main] INFO org.hibernate.orm.jpa -- HHH008540: Processing PersistenceUnitInfo [name: XmlBootstrapPU] Database JDBC URL [jdbc:h2:mem:bootstrap-namecollision;DB_CLOSE_DELAY=-1] Default catalog/schema: BOOTSTRAP-NAMECOLLISION/PUBLIC 23:40:17.814 [main] INFO org.hibernate.orm.jpa -- HHH008540: Processing PersistenceUnitInfo [name: XmlBootstrapPU] Database JDBC URL [jdbc:h2:mem:bootstrap-xml;DB_CLOSE_DELAY=-1] Default catalog/schema: BOOTSTRAP-XML/PUBLIC ``` Same persistence-unit name, two entirely different JDBC URLs, logged 104ms apart in the same JVM. The programmatic config's own properties won completely, both times a `PersistenceConfiguration` was used regardless of what XML on the classpath also happened to define under that name. Source: [`EntityManagerBootstrapTest`](../src/test/java/com/ankurm/hibernatedemo/bootstrap/EntityManagerBootstrapTest.java). Raw output: [`docs/output/bootstrap-persistenceconfiguration.txt`](output/bootstrap-persistenceconfiguration.txt) (the full log block, both `HHH008540` lines included verbatim). > **Trap**: this is good news for testing (a programmatic config can safely reuse a > production-sounding unit name without risk of accidentally inheriting production XML settings > from the classpath), but it also means a typo that *happens* to collide with a real unit name > will not get "caught" by any merge behavior -- there is none. Treat the name purely as a label > once you're on the `PersistenceConfiguration` path. ## An unconfigured name fails loudly, and the exception type has changed `Persistence.createEntityManagerFactory(name)` with no properties map and no matching `PersistenceConfiguration` or XML unit has nowhere left to look: ```java assertThatThrownBy(() -> Persistence.createEntityManagerFactory("TotallyUnknownPU")) .isInstanceOf(PersistenceException.class); ``` ``` unconfiguredUnitName: jakarta.persistence.PersistenceException: No Persistence provider for EntityManager named TotallyUnknownPU ``` **Correction to the original version of this article**: it claimed this throws `javax.persistence.PersistenceException`. That namespace is stale -- Jakarta EE moved the entire `javax.persistence.*` package to `jakarta.persistence.*` starting with Jakarta Persistence 3.0, and Hibernate 7.4.5 / Jakarta Persistence 3.2.0 (what this repo runs) only knows the `jakarta.*` form. The real, verified exception is `jakarta.persistence.PersistenceException`. (Post 4859, in the `spring-boot-demo` companion repo, had the identical `javax` → `jakarta` staleness in an unrelated exception type -- this class of correction has now shown up twice across this blog's Hibernate/JPA coverage, which suggests it's worth grepping your own older posts for `javax.persistence` if you haven't already.) Source: [`EntityManagerBootstrapTest`](../src/test/java/com/ankurm/hibernatedemo/bootstrap/EntityManagerBootstrapTest.java). Raw output: [`docs/output/bootstrap-persistenceconfiguration.txt`](output/bootstrap-persistenceconfiguration.txt). ## Creating an `EntityManagerFactory` is measurably not cheap Not a Metaspace-exhaustion reproduction -- that needs sustained, uncollectable class-loader growth across many thousands of factories, and isn't something to deliberately trigger in a shared sandbox. What **is** safely measurable in one run: factory creation timing versus `EntityManager` creation timing, from the same `PersistenceConfiguration`: ``` repeatedFactoryCreation: createEntityManagerFactory() took 1630 ms, createEntityManager() took 36 ms -- the factory call is the one doing schema validation, service registry bootstrap, and metadata scanning; the EntityManager call is comparatively trivial ``` Order of magnitude, not a precise benchmark (this ran in a shared sandbox container, and the absolute milliseconds will vary by machine) -- but the **ratio** is the point: the factory build did roughly 45x the work of creating an `EntityManager` from an already-built factory. This is the concrete, measured version of "never create an `EntityManagerFactory` per request" -- the advice usually gets repeated without a number attached to it. Source: [`EntityManagerBootstrapTest`](../src/test/java/com/ankurm/hibernatedemo/bootstrap/EntityManagerBootstrapTest.java). Raw output: [`docs/output/bootstrap-persistenceconfiguration.txt`](output/bootstrap-persistenceconfiguration.txt). > **Should you ever call this yourself?** In a Spring Boot application, essentially never -- > Spring's `LocalContainerEntityManagerFactoryBean` (what every other chapter in this repo runs > on) builds exactly one `EntityManagerFactory` at startup and hands out `EntityManager`s from it > per request/transaction via `@PersistenceContext`/`@Autowired EntityManagerFactory`. This > chapter's raw bootstrapping is for the cases genuinely outside a DI container: a standalone > Java SE batch job, a library that must not assume Spring is present, or -- as here -- a unit > test that wants to prove something about bootstrapping itself without inheriting a whole > Spring context. Going deeper: - [`jakarta.persistence.Persistence` javadoc](https://jakarta.ee/specifications/persistence/3.2/apidocs/jakarta.persistence/jakarta/persistence/persistence) (`rel="nofollow"`) - Spring's own [`LocalContainerEntityManagerFactoryBean`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.html) (`rel="nofollow"`) for how the rest of this repo actually gets its `EntityManagerFactory` ## Summary | Claim | Verified value | |---|---| | XML bootstrap via `persistence.xml` | Works, resolved purely by unit name | | `PersistenceConfiguration` (JPA 3.2) | Works with zero matching `persistence.xml` unit | | No dedicated `.jdbcUrl()` builder method | Confirmed via `javap`; JDBC settings go through `.property(PersistenceConfiguration.JDBC_URL, ...)` | | Reusing a `persistence.xml` unit's name in a programmatic config | Does NOT trigger any XML lookup or merge -- proven via `SELECT DATABASE()` and via duplicate `HHH008540` log lines with different JDBC URLs | | Unconfigured unit name | Throws `jakarta.persistence.PersistenceException` (corrected from a stale `javax.persistence.PersistenceException` claim) | | `createEntityManagerFactory()` vs `createEntityManager()` cost | Factory creation measured at roughly 45x the cost of creating an `EntityManager` from an already-built factory | [← Previous: 16 — Criteria API](16-criteria-queries.md) | [Back to README →](../README.md)