Spring Boot 2 Embedded Tomcat Jndi Datasource Configuration
Asked Answered
V

1

5

Good morning in my timezone

I already have follow this two Stack Overflow questions :

Spring Boot Using Embedded Tomcat with JNDI

and

Howto use JNDI database connection with Spring Boot and Spring Data using embedded Tomcat?

And none have worked. I am using Spring Boot 2. I want to configure embedded Tomcat Server to work with JNDI. I have try to approaches :

Snippet of code :

@SpringBootApplication
public class MyApplication {

    public static void main(String... args) { }

    @Bean
    public ServletWebServerFactory servletContainer() {
        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
            @Override
            protected void postProcessContext(Context context) {
                ContextResource resource = new ContextResource();
                resource.setName("jdbc/CCC");
                resource.setType(DataSource.class.getName());
                resource.setProperty("driverClassName", "oracle.jdbc.driver.OracleDriver");
                resource.setProperty("url", "jdbc:oracle:thin:@a77k11111188.tt.ddd.test:3000:BHJR00TT00");
                resource.setProperty("username", "user");
                resource.setProperty("password", "pass");
                context.getNamingResources().addResource(resource);
            }

            @Override
            protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
                tomcat.enableNaming();
                TomcatWebServer container = super.getTomcatWebServer(tomcat);
                for (Container child : container.getTomcat().getHost().findChildren()) {
                    if (child instanceof Context) {
                        ClassLoader contextClassLoader = ((Context) child).getLoader().getClassLoader();
                        Thread.currentThread().setContextClassLoader(contextClassLoader);
                        break;
                    }
                }
                return container;
            }

        };
        return tomcat;
    }
}

An then use the application.properties

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

Error log:

Unable to start embedded Tomcat  
Error creating bean with name 'servletEndpointRegistrar'  
Error creating bean with name 'dataSource'
 DataSourceLookupFailureException: Failed to look up JNDI DataSource with name 'java:comp/env/jdbc/CCC'  
.NamingException: Could not create resource factory instance 
 
ClassNotFoundException: org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory

Instead if I do not use the application properties and configure the datasource bean directly in the Spring Boot Application like this:

@Bean(destroyMethod = "")
    public DataSource jndiDataSource() throws IllegalArgumentException, NamingException {
        JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
        bean.setJndiName("java:comp/env/jdbc/CCC");
        bean.setProxyInterface(DataSource.class);
        bean.setLookupOnStartup(false);
        bean.afterPropertiesSet();
        return (DataSource) bean.getObject();
    } 

The error log is:

UnsatisfiedDependencyException: Error creating bean with name 'entityManagerFactory'  
BeanCreationException: Error creating bean with name 'jpaVendorAdapter' 

JndiLookupFailureException: JndiObjectTargetSource failed to obtain new target object  
NamingException: Could not create resource factory instance

In my pom i have the following dependecies

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>com.oracle</groupId>
    <artifactId>ojdbc7</artifactId>
    <version>12.1.0.2</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>  

I am out of solutions. Thanks in advance. Best regards.

Vesuvian answered 28/9, 2018 at 10:1 Comment(0)
A
6

I was also facing the same issue and most of the example on internet was using TomcatEmbeddedServletContainerFactory however after trying several things finally i was able to get jndi connection in my application.

I am still figuring out exact root cause of the problem but following is the code for your reference.

@SpringBootApplication
public class MybatisJNDISampleApplication {
    public static void main(String[] args) {
        SpringApplication.run(MybatisJNDISampleApplication.class, args);
    }

    @Bean
    public TomcatServletWebServerFactory tomcatFactory() {
        return new TomcatServletWebServerFactory() {
            @Override
            protected TomcatWebServer getTomcatWebServer(org.apache.catalina.startup.Tomcat tomcat) {
                tomcat.enableNaming();
                return super.getTomcatWebServer(tomcat);
            }

            @Override
            protected void postProcessContext(Context context) {
                ContextResource resource = new ContextResource();           
                //resource.setProperty("factory", "org.apache.tomcat.jdbc.pool.DataSourceFactory");
                resource.setName("jdbc/myDatasourceName");
                resource.setType(DataSource.class.getName());
                resource.setProperty("driverClassName", "oracle.jdbc.OracleDriver");
                resource.setProperty("url", "db_url");
                resource.setProperty("username", "db_username");
                resource.setProperty("password", "db_password");
                context.getNamingResources().addResource(resource);
            }
        };
    }
}

Following is my configuration class :

@Configuration
@MapperScan("com.sample.mybatis")
public class DataConfig {

    public final String MAPPER_LOCATIONS_PATH = "classpath:mybatis-mappers/*.xml";

    @Bean(destroyMethod="")
    public DataSource dataSource() throws IllegalArgumentException, NamingException {
        JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
        bean.setJndiName("java:comp/env/jdbc/myDatasourceName");
        //bean.setResourceRef(true); // this was previously uncommented
        bean.setProxyInterface(DataSource.class);
        //bean.setLookupOnStartup(false); // this was previously uncommented
        bean.afterPropertiesSet();
        return (DataSource)bean.getObject();
    }

    @Bean
    public DataSourceTransactionManager transactionManager() throws NamingException {
        return new DataSourceTransactionManager(dataSource());
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        configureSqlSessionFactory(sessionFactory, dataSource());
        return sessionFactory.getObject();
    }

    public void configureSqlSessionFactory(SqlSessionFactoryBean sessionFactoryBean, DataSource dataSource) throws IOException {
        PathMatchingResourcePatternResolver pathResolver = new PathMatchingResourcePatternResolver();
        sessionFactoryBean.setDataSource(dataSource);
        sessionFactoryBean.setMapperLocations(pathResolver.getResources(MAPPER_LOCATIONS_PATH));
    }
}

Hope this helps you to resolve your issue.

Abrego answered 5/2, 2019 at 10:39 Comment(2)
In general, we use to use JNDI configuration to avoid exposing the DB configuration details in general source code. You are suggesting to configure in the application start file only. In this case what would be the difference in configuring the DB connection information in the application.properties file?Ackerman
Do we need to add any dependency to resolve SqlSessionFactory and SqlSessionFactoryBean ?Abrahan

© 2022 - 2024 — McMap. All rights reserved.