Add Hibernate 7 batches 2-6, batch 7, and batch 8: mapping styles, JPA annotations, natural IDs, @Immutable, stored procedures, in-memory test databases, JNDI mocking, proxies, associations, temporal mapping, named queries, HQL, Criteria API, EntityManager bootstrapping, Ehcache 3 L2 cache configuration, HikariCP connection pooling, Hibernate Validator CDI integration, aggregate functions, sorting, pagination, interceptors, and Hibernate Search 8 (Hibernate 7.4.5.Final + Spring Boot 4.1.1 + JDK 25)
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user