Search code examples
javaspring-bootspring-mvcjnditomcat9

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


I have a SpringBootApplication, packaged as war file:

@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
public class Application extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
    
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(Application.class);
    }

}

on the application.properties:

spring.datasource.jndi-name=java:comp/env/jdbc/bonanza

but on the logs I see those messages when I deploy the war in the Tomcat 9:

Name [spring.datasource.jndi-name] is not bound in this Context. Unable to find [spring.datasource.jndi-name].. Returning null.

the logs:

12:37:53.989 [main] DEBUG o.springframework.jndi.JndiTemplate - Looking up JNDI object with name [java:comp/env/spring.datasource.jndi-name]
12:37:53.989 [main] DEBUG o.s.jndi.JndiLocatorDelegate - Converted JNDI name [java:comp/env/spring.datasource.jndi-name] not found - trying original name [spring.datasource.jndi-name]. javax.naming.NameNotFoundException: Name [spring.datasource.jndi-name] is not bound in this Context. Unable to find [spring.datasource.jndi-name].
12:37:53.990 [main] DEBUG o.springframework.jndi.JndiTemplate - Looking up JNDI object with name [spring.datasource.jndi-name]
12:37:53.991 [main] DEBUG o.s.jndi.JndiPropertySource - JNDI lookup for name [spring.datasource.jndi-name] threw NamingException with message: Name [spring.datasource.jndi-name] is not bound in this Context. Unable to find [spring.datasource.jndi-name].. Returning null.
12:37:53.995 [main] DEBUG o.springframework.jndi.JndiTemplate - Looking up JNDI object with name [java:comp/env/spring.datasource.jndi-name]
12:37:53.996 [main] DEBUG o.s.jndi.JndiLocatorDelegate - Converted JNDI name [java:comp/env/spring.datasource.jndi-name] not found - trying original name [spring.datasource.jndi-name]. javax.naming.NameNotFoundException: Name [spring.datasource.jndi-name] is not bound in this Context. Unable to find [spring.datasource.jndi-name].
12:37:53.996 [main] DEBUG o.springframework.jndi.JndiTemplate - Looking up JNDI object with name [spring.datasource.jndi-name]
12:37:53.997 [main] DEBUG o.s.jndi.JndiPropertySource - JNDI lookup for name [spring.datasource.jndi-name] threw NamingException with message: Name [spring.datasource.jndi-name] is not bound in this Context. Unable to find [spring.datasource.jndi-name].. Returning null.
12:37:53.998 [main] DEBUG o.s.c.e.PropertySourcesPropertyResolver - Found key 'spring.datasource.jndi-name' in PropertySource 'configurationProperties' with value of type String

on my tomcat9/conf/context.xml:

 <Resource  name="jdbc/bonanza" 
                auth="Container" 
                type="javax.sql.DataSource"
                maxTotal="100" 
                maxIdle="30" 
                maxWaitMillis="10000"
                username="a_usr" 
                password="Mu*7gydlcdstg100@" 
                driverClassName="com.mysql.jdbc.Driver"
                url="jdbc:mysql://172.175.77.55:3306/a_db"
        />

Solution

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