102 lines
4.6 KiB
Java
Executable File
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);
|
|
}
|
|
}
|
|
}
|