Sabir Khan
Sabir Khan

Reputation: 10142

SpringBoot JNDI datasource throws java.lang.ClassNotFoundException: org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory

Similar questions have been asked before and I went through all of those but not able to solve problem. Related Questions - Q1,Q2,Q3, Q4, Q5, Q6

I have a Spring Batch project with Spring Boot and trying to use DB connection pools. I am using embedded tomcat container with version 8.5.x.

Everything works fine if I use application.properties to specify data source and pool settings.

But when I try to use JNDI, I get exception -

Caused by: java.lang.ClassNotFoundException: org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory

I don't see any jar names tomcat-dbcp-** in Maven jars so I am not sure if I need to include any new dependency or need to set default data source factory and how to go about it.

Below is my JNDI beans set up, Question. I have blanked out certain values.

@Bean
    public TomcatEmbeddedServletContainerFactory embeddedServletContainerFactory(){
        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/myDataSource");
                resource.setType(DataSource.class.getName());
                resource.setProperty("driverClassName", "com.ibm.db2.jcc.DB2Driver");
                resource.setProperty("url", "url");
                resource.setProperty("username", "user");
                resource.setProperty("password", "*****");
                context.getNamingResources().addResource(resource);
            }
        };
    }

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

My pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <packaging>war</packaging>

    <groupId>***</groupId>
    <artifactId>***</artifactId>
    <version>1.0.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.0.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-batch</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j</artifactId>
            <version>1.2.1.RELEASE</version>
        </dependency>   

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>db2</groupId>
            <artifactId>db2jcc</artifactId>
            <version>4.0</version>
        </dependency>

        <dependency>
            <groupId>db2</groupId>
            <artifactId>db2jcc_license_cu</artifactId>
            <version>4.0</version>
        </dependency>
    </dependencies>

    <build>

        <plugins>

            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>1.4.0.RELEASE</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                <source>1.7</source>
                <target>1.7</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Upvotes: 7

Views: 14704

Answers (4)

bluscreen
bluscreen

Reputation: 41

to the preceding post. i think config for hikari might rather be sth like this:

resource.setProperty("type", "com.zaxxer.hikari.HikariDataSource");
resource.setProperty("factory", "com.zaxxer.hikari.HikariJNDIFactory");

it got me a bit further. then i was facing this exception leading to another dependency issue (missing oracle jdbc driver):

Failed to load driver class oracle.jdbc.OracleDriver in either of HikariConfig class loader or Thread context classloader
    at com.zaxxer.hikari.HikariConfig.setDriverClassName(HikariConfig.java:486) ~[HikariCP-3.4.5.jar:na]

adding or removing >>>

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'commons-codec:commons-codec'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

    providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
>>>    providedRuntime 'com.zaxxer:HikariCP'
>>>    providedRuntime 'com.oracle.database.jdbc:ojdbc8'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.apache.httpcomponents:fluent-hc'
}

to my gradle buildfile didnt make a difference. i also tried implementation and runtimeOnly scopes but it doesnt seem to affect the problem.

if anyone has ideas on how to propagate oracle jdbc driver (or basically any jar file, preferrably using gradle) to the spring boot embedded tomcat, please share your wisdom.

seems to be related to this: https://developpaper.com/maven-magic-hall-the-pits-on-which-oracle-jdbc-driver-depends/ maybe using this maven workaround it might work but i don't want to switch back to maven just for this.

Upvotes: 0

davidxxx
davidxxx

Reputation: 131346

You have multiple choices :

  • using the DBCP 2 datasource that is the default (you don't want to use DBCP 1 that is outdated and less efficient).
  • using the Tomcat JDBC datasource.
  • using any other datasource : for example HikariCP.

1) To use Apache JDBC datasource, you don't need to add any dependency as it is already provided in the Tomcat Spring Boot starter but you have to change the default factory class to org.apache.tomcat.jdbc.pool.DataSourceFactory to use it.
You can do it in the resource declaration : resource.setProperty("factory", "org.apache.tomcat.jdbc.pool.DataSourceFactory"); I will explain below where add this line.

2) To use DBCP 2 datasource (which is actually expected by default) a dependency is required:

<dependency>
  <groupId>org.apache.tomcat</groupId>
  <artifactId>tomcat-dbcp</artifactId>
  <version>8.5.4</version>
</dependency>

Of course, adapt the artifact version according to your Spring Boot Tomcat embedded version.

3) To use any other datasource, I will illustrate with HikariCP, add the required dependency if not already present in your configuration (it may be for HikariCP if you rely on persistence starters of Spring Boot) such as :

<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>3.1.0</version>
</dependency>

and specify the factory that goes with in the resource declaration :

resource.setProperty("factory", "com.zaxxer.hikari.HikariDataSource");

For example with PostgreSQL and a DBCP 2 datasource, don't specify any factory as it is the default :

    @Override 
    protected void postProcessContext(Context context) {            
        ContextResource resource = new ContextResource();
        //...
        context.getNamingResources()
               .addResource(resource);          
    }

Here the variants for Tomcat JDBC and HikariCP datasource.

In postProcessContext() set the factory property as explained early for Tomcat JDBC ds :

    @Override 
    protected void postProcessContext(Context context) {
        ContextResource resource = new ContextResource();       
        //...
        resource.setProperty("factory", "org.apache.tomcat.jdbc.pool.DataSourceFactory");
        //...
        context.getNamingResources()
               .addResource(resource);          
    }
};

and for HikariCP :

    @Override 
    protected void postProcessContext(Context context) {
        ContextResource resource = new ContextResource();       
        //...
        resource.setProperty("factory", "com.zaxxer.hikari.HikariDataSource");
        //...
        context.getNamingResources()
               .addResource(resource);          
    }
};

Upvotes: 2

Sabir Khan
Sabir Khan

Reputation: 10142

I solved the problem by setting factory attribute in my Resource Definition. resource.setProperty("factory", "org.apache.tomcat.jdbc.pool.DataSourceFactory");

@Bean
public TomcatEmbeddedServletContainerFactory embeddedServletContainerFactory(){
    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/myDataSource");
            resource.setType(DataSource.class.getName());
            resource.setProperty("factory", "org.apache.tomcat.jdbc.pool.DataSourceFactory");
            resource.setProperty("driverClassName", "com.ibm.db2.jcc.DB2Driver");
            resource.setProperty("url", "url");
            resource.setProperty("username", "user");
            resource.setProperty("password", "*****");
            context.getNamingResources().addResource(resource);
        }
    };
}

As per tomcat 8 documentation, it is supposed to automatically infer db pool factory type by looking at DataSource type and somehow it defaults to DBCP factory and that class is not there in my class path.

I guess so issue can be solved by making tomcat-dbcp-** jars available but I am not sure how to do that with spring boot or even if that is possible with spring boot.

What I find weird is Spring Boot not including tomcat-dbcp dependencies as part of starter POM but using DBCP DataSource factory as default factory.

Upvotes: 16

Liping Huang
Liping Huang

Reputation: 4476

The “Starter POM” no longer includes jndi reltead dependencies, if you are using Tomcat/Jetty/etc... with JNDI you will now need to directly add this dependency yourself.

Then configure the JNDI in your application.properties file spring.datasource.jndi-name=java:comp/env/jdbc/yourname

For your exception, you need add the tomcat-dbcp into your pom.xml file.

But you can check your project dependencies, if you use the spring-boot-starter-jdbc or spring-boot-starter-data-jpa ‘starters’ you will automatically get a dependency to "tomcat-jdbc".

Upvotes: 3

Related Questions