Spring Boot JNDI 数据源抛出 java.lang.ClassNotFoundException: org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory

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

之前有人问过类似的问题,我都解决了所有问题,但无法解决问题。相关问题 - Q1,Q2,Q3, Q4, Q5, Q6

我有一个 Spring 批处理项目 Spring 引导并尝试使用数据库连接池。我正在使用版本 8.5.x 的嵌入式 tomcat 容器。

如果我使用 application.properties 指定数据源和池设置,一切正常。

但是当我尝试使用 JNDI 时,出现异常 -

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

我在 Maven jar 中没有看到任何 jar 名称 tomcat-dbcp-** 所以我不确定我是否需要包含任何新的依赖项或需要设置默认数据源工厂以及如何去做。

下面是我的 JNDI bean 设置,Question。我已经删除了某些值。

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

我的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>

“Starter POM”不再包含 jndi reltead 依赖项,如果您 正在使用 Tomcat/Jetty/etc... 与 JNDI 您现在需要自己直接添加此依赖项。

然后在 application.properties 文件中配置 JNDI spring.datasource.jndi-name=java:comp/env/jdbc/yourname

对于例外情况,您需要将 tomcat-dbcp 添加到 pom.xml 文件中。

但是你可以检查你的项目依赖关系,如果你使用 spring-boot-starter-jdbc 或者 spring-boot-starter-data-jpa 'starters' 你 将自动获得对 "tomcat-jdbc".

的依赖

我通过在 Resource 定义中设置 factory 属性解决了这个问题。 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);
        }
    };
}

根据 tomcat 8 文档,它应该通过查看 DataSource 类型自动推断数据库池工厂类型,并且不知何故它默认为 DBCP 工厂而 class 不是在我的 class 路径中。

我想这个问题可以通过提供 tomcat-dbcp-** jar 来解决,但我不确定如何使用 spring 启动或者即使 spring 启动可以做到这一点.

我觉得奇怪的是 Spring 引导不包括 tomcat-dbcp 依赖项作为启动 POM 的一部分,而是使用 DBCP 数据源工厂作为默认工厂。

您有多种选择:

  • 使用默认的 DBCP 2 数据源(您不想使用过时且效率较低的 DBCP 1)。
  • 使用 Tomcat JDBC 数据源。
  • 使用任何其他数据源:例如 HikariCP。

1) 要使用 Apache JDBC 数据源,您不需要添加任何依赖项,因为它已经在 Tomcat Spring 引导程序中提供,但您必须更改默认工厂 class 到 org.apache.tomcat.jdbc.pool.DataSourceFactory 以使用它。
您可以在资源声明中执行此操作: resource.setProperty("factory", "org.apache.tomcat.jdbc.pool.DataSourceFactory"); 我将在下面解释添加此行的位置。

2) 要使用 DBCP 2 数据源(默认情况下实际上是预期的)需要依赖项:

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

当然要根据你的SpringBootTomcat嵌入式版本适配神器版本。

3) 要使用任何其他数据源,我将使用 HikariCP 进行说明,如果您的配置中还没有添加所需的依赖项(如果您依赖 Spring Boot 的持久性启动器,则可能适用于 HikariCP)例如:

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

并在资源声明中指定工厂:

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

例如,对于 PostgreSQL 和 DBCP 2 数据源,不要指定任何工厂,因为它是默认设置:

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

这里是 Tomcat JDBC 和 HikariCP 数据源的变体。

postProcessContext() 中设置工厂 属性 如前所述 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);          
    }
};

对于 HikariCP:

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

到前面的post。我认为 hikari 的配置可能更像是这样的:

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

它让我更进一步。然后我遇到了导致另一个依赖性问题的异常(缺少 oracle jdbc 驱动程序):

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]

添加或删除 >>>

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'
}

我的 gradle 构建文件没有什么不同。我也尝试了 implementation 和 runtimeOnly 范围,但它似乎没有影响问题。

如果有人对如何将 oracle jdbc 驱动程序(或基本上任何 jar 文件,最好使用 gradle)传播到 spring 引导嵌入式 tomcat,请分享你的智慧。

似乎与此有关:https://developpaper.com/maven-magic-hall-the-pits-on-which-oracle-jdbc-driver-depends/ 也许使用这个 maven 解决方法可能会起作用,但我不想为此切换回 maven。