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. * *

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); } } }