2

我正在尝试使用 tomcat jdbc 连接池,并在我的应用程序 context.xml 文件中定义它。

<Context>
    <Resource auth="Container" name="jdbc/iup" type="javax.sql.DataSource"
              maxActive="300" maxIdle="30" maxWait="20000"
              username="${db.username}" password="${db.password}" driverClassName="net.sf.log4jdbc.DriverSpy"
              url="jdbc:log4jdbc:sqlserver://${db.server};databaseName=${db.name}"/>
</Context>

net.sf.log4jdbc.DriverSpy定义在 中log4jdbc4-1.2.jar,它放在我的应用程序 lib文件夹中。它对我来说很好。但是这里说,带有驱动程序类的 jar 应该只放在 tomcat lib 文件夹中。

Tomcat 使用它的BasicDataSource类来加载驱动程序:

if (driverClassName != null) {
            try {
                try {
                    if (driverClassLoader == null) {
                        Class.forName(driverClassName);
                    } else {
                        Class.forName(driverClassName, true, driverClassLoader);
                    }
                } catch (ClassNotFoundException cnfe) {
                    driverFromCCL = Thread.currentThread(
                            ).getContextClassLoader().loadClass(
                                    driverClassName);
                }
            } catch (Throwable t) {
                String message = "Cannot load JDBC driver class '" +
                    driverClassName + "'";
                logWriter.println(message);
                t.printStackTrace(logWriter);
                throw new SQLNestedException(message, t);
            }
        }

driverClassLoader为 null,并且驱动程序类正试图通过Class.forName(driverClassName). 据我了解,在这种情况下,驱动程序类正在加载与原样相同的类加载器实例BasicDataSource。这是StandardClassLoader,如果我的 jar 在 tomcat 库中,它将加载类。在我的情况下,异常被抛出并被Thread.currentThread().getContextClassLoader()使用,它是WebappClassLoader实例,可以从 webapp lib 加载类,它确实如此。所以我很困惑。为什么说,如果我使用来自容器资源的数据源,我必须将我的驱动程序类放在 tomcat 库中。

请解释一下,谢谢

4

1 回答 1

6

Tomcat 自动将容器管理的连接池添加到每个类型的资源jaxaz.sql.DataSource。提供此池的库(Commons DBCP 的包重命名版本)由共享类加载器(在默认配置中与公共加载器相同)加载。池实现需要能够加载已配置的 JDBC 驱动程序,并且共享(和通用)加载器对 Web 应用程序类加载器不可见。因此,带有 JDBC 驱动程序的 JAR 需要位于$CATALINA_BASE/lib目录中,以便加载。

但是,从r754776 开始,如果 DBCP 无法加载指定的驱动程序,它会退回到 Thread 的上下文类加载器。如果线程上下文类加载器设置为 Web 应用程序的类加载器,则可以加载驱动程序。此更改包含在DBCP 1.31.4后续版本中,这意味着它包含在5.5.30后续版本、6.0.27后续版本和每个7.0.x版本中。它也将出现在每个8.0.x版本中。

使用MarkMail对查询量的一个相当不科学的观察表明,Tomcat 用户邮件列表中的问题有所减少,ClassNotFoundException但这同样可能取决于人们对这个问题的了解程度更高。

我想最根本的问题是这可靠吗?如果在DataSource线程上下文类加载器是 Web 应用程序类加载器时总是实例化它,那么它将是可靠的。对这些资源的访问是通过JNDI的,这取决于正确设置的线程上下文类加载器。如果不是 -JNDI将无法找到 Web 应用程序资源。在此基础上,这应该可以正常工作。

全球资源(显然)仍需要JDBC驱动程序位于$CATALINA_HOME/lib

可能导致问题的情况是如果JDBC驱动程序JAR存在于$CATALINA_HOME/lib and WEB-INF/lib. 如果 Web 应用程序尝试转换为特定于数据库的对象,那么事情将会失败,因为这将尝试将由共享加载器加载的类转换为由 Web 应用程序类加载器加载的同名类,该类加载器将始终失败。

简而言之:

  • 长期存在的建议不要将 JDBC 驱动程序放入WEB-INF/lib$CATALINA_[HOME|BASE]/lib保留
  • 从那时6.0.27起,可以将 JDBC 驱动程序打包到 Web 应用程序中,并且一切正常。

为最初的错误/不完整答案道歉。这不是我第一次完全忘记我所做的提交,我怀疑这不会是最后一次。

于 2013-10-17T15:43:11.087 回答