153 lines
8.1 KiB
Java
153 lines
8.1 KiB
Java
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.
|
|
*
|
|
* <p>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.
|
|
*
|
|
* <p>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", "[email protected]");
|
|
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("[email protected]");
|
|
reader.close();
|
|
DEMO.info("xmlBootstrap: persisted and reloaded user id={}", id);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
void programmaticBootstrap_needsNoPersistenceXmlUnitOnClasspath() {
|
|
// "ProgrammaticPU" has NO matching <persistence-unit> 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", "[email protected]");
|
|
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<Object> 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);
|
|
}
|
|
}
|