Files
hibernate-demo/src/test/java/com/ankurm/hibernatedemo/jndi/HibernateJndiDataSourceTest.java
T

102 lines
4.6 KiB
Java
Executable File

package com.ankurm.hibernatedemo.jndi;
import static org.assertj.core.api.Assertions.assertThat;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NameAlreadyBoundException;
import org.h2.jdbcx.JdbcDataSource;
import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Backs docs/10-mocking-jndi-datasources.md, chapter "Getting Hibernate itself to resolve a JNDI DataSource by
* name". Deliberately bypasses Spring Boot's autoconfiguration entirely and goes straight at
* Hibernate's own {@code hibernate.connection.datasource} setting (see
* {@code org.hibernate.cfg.JdbcSettings.DATASOURCE} -- confirmed present in 7.4.5.Final via
* javap, docs/output/jndi-hibernate-datasource-setting-javap.txt), which is the exact mechanism
* the article's original code was trying to exercise through Spring.
*
* <p>A separate attempt to get Spring Boot's own {@code spring.datasource.jndi-name} property
* to resolve through a full {@code @SpringBootTest} context (with {@code spring-boot-starter-web}
* on the test classpath and {@code simple-jndi}'s {@code MemoryContextFactory}) reproduced a
* {@code NameNotFoundException} at Spring bean-creation time that this test's plain-Hibernate
* bootstrap does not hit at all -- see the findings report for what was and was not diagnosed
* about that gap. This test is the one that stayed green and demonstrates the real mechanism:
* a JNDI-bound {@code DataSource}, resolved by name, driving a working {@code SessionFactory}.
*/
class HibernateJndiDataSourceTest {
private static final Logger DEMO = LoggerFactory.getLogger("DEMO");
private static final String JNDI_NAME = "jdbc/HibernateTestDS";
@BeforeEach
void bindDataSource() throws Exception {
System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.osjava.sj.MemoryContextFactory");
System.setProperty("org.osjava.sj.jndi.shared", "true");
JdbcDataSource ds = new JdbcDataSource();
ds.setURL("jdbc:h2:mem:hibernate-jndi-test;DB_CLOSE_DELAY=-1");
ds.setUser("sa");
ds.setPassword("");
Context ctx = new InitialContext();
createSubcontextIfAbsent(ctx, "jdbc");
ctx.rebind(JNDI_NAME, ds);
}
@AfterEach
void unbindAndClearFactory() throws Exception {
try {
new InitialContext().unbind(JNDI_NAME);
} catch (Exception ignored) {
}
System.clearProperty(Context.INITIAL_CONTEXT_FACTORY);
System.clearProperty("org.osjava.sj.jndi.shared");
}
// Real-world hygiene, discovered the hard way: simple-jndi's shared MemoryContext is a
// single JVM-wide namespace. If a DIFFERENT test class in the same run already created the
// "jdbc" subcontext and did not tear it down, a plain createSubcontext("jdbc") here throws
// NameAlreadyBoundException before this test even gets to the DataSource bind -- this is
// the exact cross-test pollution CrossTestPollutionTest demonstrates on purpose, caught
// here by accident the first time this suite ran as a whole rather than one class at a time.
private static void createSubcontextIfAbsent(Context ctx, String name) throws Exception {
try {
ctx.createSubcontext(name);
} catch (NameAlreadyBoundException alreadyThere) {
// fine -- another test in this JVM already created it
}
}
@Test
void hibernateResolvesDataSourceByJndiName_andBuildsAWorkingSessionFactory() {
StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
.applySetting("hibernate.connection.datasource", JNDI_NAME)
.applySetting("hibernate.hbm2ddl.auto", "create-drop")
.build();
try {
SessionFactory sessionFactory = new Configuration().buildSessionFactory(registry);
assertThat(sessionFactory).isNotNull();
try (var session = sessionFactory.openSession()) {
Integer result = session.createNativeQuery("SELECT 1", Integer.class).getSingleResult();
DEMO.info("Hibernate SessionFactory built from JNDI name '{}' ran SELECT 1 -> {}", JNDI_NAME, result);
assertThat(result).isEqualTo(1);
} finally {
sessionFactory.close();
}
} finally {
StandardServiceRegistryBuilder.destroy(registry);
}
}
}