How to Use Jndi Datasource Provided by Tomcat in Spring

How to use JNDI DataSource provided by Tomcat in Spring?

If using Spring's XML schema based configuration, setup in the Spring context like this:

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee" xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd">
...
<jee:jndi-lookup id="dbDataSource"
jndi-name="jdbc/DatabaseName"
expected-type="javax.sql.DataSource" />

Alternatively, setup using simple bean configuration like this:

<bean id="DatabaseName" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/DatabaseName"/>
</bean>

You can declare the JNDI resource in tomcat's server.xml using something like this:

<GlobalNamingResources>
<Resource name="jdbc/DatabaseName"
auth="Container"
type="javax.sql.DataSource"
username="dbUser"
password="dbPassword"
url="jdbc:postgresql://localhost/dbname"
driverClassName="org.postgresql.Driver"
initialSize="20"
maxWaitMillis="15000"
maxTotal="75"
maxIdle="20"
maxAge="7200000"
testOnBorrow="true"
validationQuery="select 1"
/>
</GlobalNamingResources>

And reference the JNDI resource from Tomcat's web context.xml like this:

  <ResourceLink name="jdbc/DatabaseName"
global="jdbc/DatabaseName"
type="javax.sql.DataSource"/>

Reference documentation:

  • Tomcat 8 JNDI Datasource HOW-TO
  • Tomcat 8 Context Resource Links Reference
  • Spring 4 JEE JNDI Lookup XML Schema Reference
  • Spring 4 JndiObjectFactoryBean Javadoc

Edit: This answer has been updated for Tomcat 8 and Spring 4. There have been a few property name changes for Tomcat's default datasource resource pool setup.

How do you use a Tomcat JNDI JDBC datasource in Spring Boot

@Bean
public DataSource dataSource() {
JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
DataSource dataSource = dataSourceLookup.getDataSource("jdbc/apolloJNDI");
return dataSource;
}

No "java:comp/env/" its needed because JndiDataSourceLookup internaly calls convertJndiName that add this part. In other clases you should set the complete path.

Configure DataSource Using JNDI Using external Tomcat 9 Server: Spring Boot

As the error suggests, spring boot cannot find the key in the JNDI lookup. JNDI is disabled in Spring boot's embedded Tomcat so you would need to enable it using Tomcat#enableNaming and once that is done you would need to create a lookup entry in JNDI. You can refer to the below code which I copied from one of the spring boot project maintainers repository GitHub repo JNDI-Tomcat

@Bean
public TomcatEmbeddedServletContainerFactory tomcatFactory() {
return new TomcatEmbeddedServletContainerFactory() {

@Override
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
Tomcat tomcat) {
tomcat.enableNaming();
return super.getTomcatEmbeddedServletContainer(tomcat);
}

@Override
protected void postProcessContext(Context context) {
ContextResource resource = new ContextResource();
resource.setName("jdbc/bonanza");
resource.setType(DataSource.class.getName());
resource.setProperty("driverClassName", "your.db.Driver");
resource.setProperty("url", "jdbc:yourDb");

context.getNamingResources().addResource(resource);
}
};
}

[Edit]

As you are not using embedded tomcat server, you can configure JNDI by configuring it using tomcat config files:

In server.xml, create a Resource under <GlobalNamingResources>

<Resource auth="Container" driverClassName="..." 
maxActive="..."
maxIdle="..."
maxWait="..."
name="jdbc/bonanza"
username="..."
password="..."
type="..."
url="..."/>

In Context.xml, you can link the resource

<context>
<ResourceLink auth="Container" name="jdbc/bonanza" global="jdbc/bonanza" type="javax.sql.DataSource" />
</context>

Also, make sure you are not starting the application using the spring-boot main method. You need to build the war file using maven/gradle and then deploy it to the tomcat and test it.

How to create JNDI context in Spring Boot with Embedded Tomcat Container

By default, JNDI is disabled in embedded Tomcat which is causing the NoInitialContextException. You need to call Tomcat.enableNaming() to enable it. The easiest way to do that is with a TomcatEmbeddedServletContainer subclass:

@Bean
public TomcatEmbeddedServletContainerFactory tomcatFactory() {
return new TomcatEmbeddedServletContainerFactory() {

@Override
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
Tomcat tomcat) {
tomcat.enableNaming();
return super.getTomcatEmbeddedServletContainer(tomcat);
}
};
}

If you take this approach, you can also register the DataSource in JNDI by overriding the postProcessContext method in your TomcatEmbeddedServletContainerFactory subclass.

context.getNamingResources().addResource adds the resource to the java:comp/env context so the resource's name should be jdbc/mydatasource not java:comp/env/mydatasource.

Tomcat uses the thread context class loader to determine which JNDI context a lookup should be performed against. You're binding the resource into the web app's JNDI context so you need to ensure that the lookup is performed when the web app's class loader is the thread context class loader. You should be able to achieve this by setting lookupOnStartup to false on the jndiObjectFactoryBean. You'll also need to set expectedType to javax.sql.DataSource:

<bean class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/mydatasource"/>
<property name="expectedType" value="javax.sql.DataSource"/>
<property name="lookupOnStartup" value="false"/>
</bean>

This will create a proxy for the DataSource with the actual JNDI lookup being performed on first use rather than during application context startup.

The approach described above is illustrated in this Spring Boot sample.



Related Topics



Leave a reply



Submit