尝试使用JDBC驱动程序访问数据库时,我看到一些奇怪的行为。这是代码片段:
LOGGER.debug("driver is " + driver);
try {
Class.forName(driver);
LOGGER.debug("got driver");
} catch (Throwable t) {
LOGGER.debug("throwable getting driver " + driver);
t.printStackTrace(System.out);
throw t;
}
当我运行它时,这是我在堆栈跟踪中看到的。
08:20:00.417 [main] DEBUG - driver is com.sybase.jdbc4.jdbc.SybDriver
08:20:00.604 [main] DEBUG - throwable getting driver com.sybase.jdbc4.jdbc.SybDriver
java.lang.NoClassDefFoundError: Could not initialize class oracle.jdbc.OracleDriver
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:398)
at java.sql/java.sql.DriverManager.isDriverAllowed(DriverManager.java:555)
at java.sql/java.sql.DriverManager.isDriverAllowed(DriverManager.java:547)
at java.sql/java.sql.DriverManager.getDrivers(DriverManager.java:449)
at java.sql/java.sql.DriverManager.getDrivers(DriverManager.java:426)
at com.sybase.jdbc4.jdbc.SybDriver.registerWithDriverManager(Unknown Source)
at com.sybase.jdbc4.jdbc.SybDriver.<init>(Unknown Source)
at com.sybase.jdbc4.jdbc.SybDriver.<clinit>(Unknown Source)
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:315)
... my code
所以我可以看到我想要的驱动程序名称是com.sybase.jdbc4.jdbc.SybDriver,这是正确的,但由于某种原因,DriverManager正在寻找oracle.jdbc.OracleDriver。
到底是怎么回事?这段代码多年来一直运行良好,我能想到的唯一其他相关信息是我最近将这台机器上的JDK升级为Open JDK 11。
>java -version
openjdk version "11.0.2" 2019-01-15
OpenJDK Runtime Environment 18.9 (build 11.0.2+9)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.2+9, mixed mode)
这不是一个完整的答案,但它似乎是一个类加载问题与自动驱动程序加载相结合。
当您明确使用Class.forName
加载JDBC驱动程序时,驱动程序应该使用java.sql.DriverManager
注册自己。
查看堆栈跟踪,具体来说:
at java.sql/java.sql.DriverManager.getDrivers(DriverManager.java:426) at com.sybase.jdbc4.jdbc.SybDriver.registerWithDriverManager(Unknown Source) at com.sybase.jdbc4.jdbc.SybDriver.<init>(Unknown Source) at com.sybase.jdbc4.jdbc.SybDriver.<clinit>(Unknown Source) at java.base/java.lang.Class.forName0(Native Method)
Sybase驱动程序在(?)注册之前错误地检查当前已注册的驱动程序(使用DriverManager.getDrivers
)。更糟糕的是,它是从驱动程序构造函数而不是静态初始化程序执行此操作,这可能会导致驱动程序加载死锁。正确的行为驱动程序应该从JDBC 4.3第9.2节中指定的静态初始化程序调用DriverManager.registerDriver
:
JDBC驱动程序必须实现
Driver
接口,并且实现必须包含将在加载驱动程序时调用的静态初始化程序。该初始化程序使用DriverManager
注册其自身的新实例,如代码示例9-1所示。public class AcmeJdbcDriver implements java.sql.Driver { static { java.sql.DriverManager.registerDriver(new AcmeJdbcDriver()); } ... }
代码示例9-1实现
java.sql.Driver
的驱动程序的静态初始化程序示例加载
Driver
实现时,静态初始化程序将自动注册驱动程序的实例。
因为DriverManager.getDrivers
被调用,它将自动加载META-INF/service/java.sql.Driver
文件(以及系统属性jdbc.drivers
中的那些)的类路径上的驱动程序。
看起来像是以这种方式发现并加载了Oracle JDBC驱动程序,但是检查isDriverAllowed
中当前类加载器中的驱动程序是否可用时,NoClassDefFoundError
失败(检查捕获异常,但不是错误,也许它应该)。
作为解决方法,您应该从类路径中删除Oracle JDBC驱动程序,或者找出它在当前类加载器中不可用的原因。
作为进一步的诊断,尝试在您的代码中调用DriverManager.getDrivers()
,Class.forName("oracle.jdbc.Driver
甚至new oracle.jdbc.Driver()
,看看会发生什么。
您可能还想检查Sybase驱动程序的版本,以及是否有更新版本不执行此检查,尽管这可能只会导致错误发生在代码中的其他位置。
所以我做了一些进一步的调查如下。我写了一个最小的完整例子:
import java.sql.*;
import java.util.*;
public class TestDrivers {
public static void main(String[] args) {
try {
Enumeration<Driver> driverEnumeration = DriverManager.getDrivers();
while (driverEnumeration.hasMoreElements()) {
Driver driver = driverEnumeration.nextElement();
System.out.println("driver is " + driver.getClass().getName());
}
} catch (Throwable t) {
System.out.println("throwable getting drivers");
t.printStackTrace(System.out);
throw t;
}
}
}
然后我用四个不同的类路径运行这个例子,结果如下:
因此,如果存在Oracle和Sybase JDBC驱动程序,则会发生一些奇怪的交互。我还尝试了Oracle ojdbc7.jar和ojdbc8.jar文件,结果基本相同。这是完整的堆栈跟踪:
java.lang.NoClassDefFoundError: Could not initialize class oracle.jdbc.OracleDriver
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:398)
at java.sql/java.sql.DriverManager.isDriverAllowed(DriverManager.java:555)
at java.sql/java.sql.DriverManager.isDriverAllowed(DriverManager.java:547)
at java.sql/java.sql.DriverManager.getDrivers(DriverManager.java:449)
at java.sql/java.sql.DriverManager.getDrivers(DriverManager.java:426)
at com.javamarket.drivers.TestDrivers.main(TestDrivers.java:10)
Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class oracle.jdbc.OracleDriver
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:398)
at java.sql/java.sql.DriverManager.isDriverAllowed(DriverManager.java:555)
at java.sql/java.sql.DriverManager.isDriverAllowed(DriverManager.java:547)
at java.sql/java.sql.DriverManager.getDrivers(DriverManager.java:449)
at java.sql/java.sql.DriverManager.getDrivers(DriverManager.java:426)
at com.javamarket.drivers.TestDrivers.main(TestDrivers.java:10)
最后,我在运行JDK 8的另一台机器上尝试了这一点,并且所有四个类路径变体都成功运行。