在jdk1.6中java.sql.DriverManager明确指出,在JDBC 4.0 Drivers 版本及以后版本不再需要使用Class.forName() 显式地加载 JDBC 驱动程序。
mysql JDBC Driver源文件
public class Driver extends NonRegisteringDriver implementsjava.sql.Driver {
//
// Register ourselves with theDriverManager
//JDBC4.0Drivers以前,使用Class.forName("com.mysql.jdbc.driver") 显示加载 驱动程序时就会调用这个静态代码块。
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch(SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
/**
* Construct a new driverand register it with DriverManager
*
* @throws SQLException
* if a database error occurs.
*/
public Driver() throwsSQLException {
// Required forClass.forName().newInstance()
}
}
javaSE项目(支持不用forName())
不用forName()显示调用的代码,也能正确连接**************************(支持不用forName())
//连接数据库的URL
String url ="jdbc:mysql://localhost:3306/test";
//设置用户名和密码
Properties props = new Properties();
props.setProperty("user","user");
props.setProperty("password","password");
//应用程序不再需要使用 Class.forName() 显式地加载 JDBC 驱动程序。
//在调用getConnection 方法时,DriverManager 会试着从初始化时加载的那些驱动程序以及使用与当前 applet
// 或应用程序相同的类加载器显式加载的那些驱动程序中查找合适的驱动程序。
//连接到具体的数据库(根据url中的连接规范判断连接驱动程序)
Connection connection =DriverManager.getConnection(url, props);
*************************************
那不用显示调用forName(Driver)连接数据库是什么时候加载java.sql.jdbc.Driver的呢?
让我们看一下
java.sql.DriverManager的源代码。
在java.sql.DriverManager类中有一静态码块,里面有 loadInitialDrivers()方法。
/**
* Load the initial JDBCdrivers by checking the System property
* jdbc.properties and thenuse the {@code ServiceLoader} mechanism
*/
static {
loadInitialDrivers(); //用来初始化相关的Drivers
println("JDBCDriverManager initialized");
}
loadInitialDrivers();源代码
private static void loadInitialDrivers() {
Stringdrivers;
//省略从META-INF/services/java.sql.Driver文件中或从读取Driver驱动等代码
。。。
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try{
println("DriverManager.Initialize: loading " +aDriver);
//这里有forName()语句,也就是说,再调用DriverManage静态方法时必定会调用这里的语句。
//所以JDBC 4.0 Drivers及以后版本是不需要显示调用forName语句的。
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
}catch (Exception ex) {
println("DriverManager.Initialize: load failed: " +ex);
}
}
}
上面在java 项目中完全像JDK中介绍的一样,运行没问题。
java web项目(不支持不用forName())
今天在java web项目中用tomcat服务器试了一下,出现
java.lang.RuntimeException: java.sql.SQLException:No suitable driver found错误
也就是com.mysql.jdbc.Driver类没有找到,我已经把mysql-connector-java-5.1.41-bin.jar放到了WebRoot/WEB-INF/lib/目录下,确提示没有找到匹配的Driver驱动。
加上Class.forName(com.mysql.jdbc.Driver),问题解决,说明mysql的JDBC连接.jar是生效的。
为什么WEB项目中DriverManager静态代码块中的loadInitialDrivers()不能找到自己的com.mysql.jdbc.Driver呢?
2020-1-3更新:
先分析一下的Driver的是通过哪种类加载器来加载的:
1. 不显示的调用Class.forName(com.mysql.jdbc.Driver)时,最终是调用ClassLoader.getSystemClassLoader()类加载器来加载(默认是AppClassLoader,注意:这里没有通过debug来确认是否是AppClassLoader)。
2. 显示的调用Class.forName(com.mysql.jdbc.Driver)时是由tomcat的Webapp加载器来加载。
在DriverManager.getConnection(...)时会调用下面的私有方法getConnection(..),其中里面有一个isDriverAllowed(aDriver.driver, callerCL)方法,其中callerCL为调用此方法的对象的类加载器(也就是tomcat的Webapp类加载器)。它会判断用callerCL加载的aClass与传入的driver.getClass()是否为同一个Class<Driver>,如果不相同就会抛出异常;
因为虽然都是加载com.mysql.jdbc.Driver,但通过不同类加载器加载的Class是不相等的,所以当第1种情况不显示调用Class.forName(com.mysql.jdbc.Driver)时,isDriverAllowed(aDriver.driver, callerCL)返回为false,就会抛出java.lang.RuntimeException: java.sql.SQLException:No suitable driver found异常。
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
...
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
// if we got here nobody could connect.
if (reason != null) {
println("getConnection failed: " + reason);
throw reason;
}
println("getConnection: no suitable driver found for "+ url);
throw new SQLException("No suitable driver found for "+ url, "08001");
}
private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
boolean result = false;
if(driver != null) {
Class<?> aClass = null;
try {
aClass = Class.forName(driver.getClass().getName(), true, classLoader);
} catch (Exception ex) {
result = false;
}
result = ( aClass == driver.getClass() ) ? true : false;
}
return result;
}
2020-4-7更新:如springboot分模块开发:web模块(用内嵌tomcat)与core模块
在应用分模块的情况下:如core模块包含servic与dao,core模块的pom.xml中含有
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
</dependency>
web模块包含core模块的pom.xml中含有
<dependency>
<groupId>com.test</groupId>
<artifactId>core</artifactId>
<version>1.0.0</version>
</dependency>
maven会把core模块的依赖一块导入进web模块,按道理是可以加载com.mysql.jdbc.Driver,
但此时启动项目就会出现错误提示(一般都是IDE开发环境不同导致的,我猜想是maven/gradle等构建插件环境不同导致的,至少在我的IDE环境下是这个情况):Caused by: java.lang.ClassNotFoundException: com.mysql.jdbc.Driver错误。
但如果web模块导入mysql依赖且scope用provided(就是在编译是参与,打包时不参与添加到jar包内):
<dependency>
<groupId>com.test</groupId>
<artifactId>core</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
<scope>provided</scope>
</dependency>
这样就不会提示Caused by: java.lang.ClassNotFoundException: com.mysql.jdbc.Driver错误。
当添加一个是scope为provided的MySQL驱动依赖,provided是编译是参与,打包时不参与,但问题竟然解决了,tomcat类加载器加载时加载到了com.mysql.jdbc.Driver驱动。应该是maven在编译阶段core模块添加了对mysql驱动的应用,所以在web项目启动时类加载加载到了mysql驱动。maven不太精通,所以也不具体分析了。
避免上面的情况发生:
gradle可以用gradle包装器解决。
maven我想有插件可以解决吧,maven不是很懂。
-------------------------------------------------------------
引用:jdk 1.6中文版java.sql.DriverManager
public class DriverManager extends Object
管理一组JDBC 驱动程序的基本服务。
注:DataSource接口是 JDBC 2.0 API 中的新增内容,它提供了连接到数据源的另一种方法。使用 DataSource 对象是连接到数据源的首选方法。(这里看了com.mysql.jdbc.jdbc2.optional.MysqlDataSource源码,也就是利用反射创建com.mysql.jdbc.Driver类,只是不用注册DriverManager了;而且创建驱动获得的连接跟是此方法获得的连接是一样的java.sql.Connection)
作为初始化的一部分,DriverManager 类会尝试加载在 "jdbc.drivers" 系统属性中引用的驱动程序类。这允许用户定制由他们的应用程序使用的 JDBC Driver。例如,在 ~/.hotjava/properties 文件中,用户可以指定:
jdbc.drivers=foo.bah.Driver:wombat.sql.Driver:bad.taste.ourDriver
DriverManager 类的方法 getConnection 和 getDrivers 已经得到提高以支持 Java Standard Edition ServiceProvider 机制。 JDBC 4.0 Drivers必须包括 META-INF/services/java.sql.Driver 文件。此文件包含 java.sql.Driver 的 JDBC 驱动程序实现的名称。例如,要加载 my.sql.Driver 类,META-INF/services/java.sql.Driver文件需要包含下面的条目:
my.sql.Driver
应用程序不再需要使用 Class.forName() 显式地加载 JDBC 驱动程序。当前使用 Class.forName() 加载 JDBC 驱动程序的现有程序将在不作修改的情况下继续工作。
在调用 getConnection 方法时,DriverManager 会试着从初始化时加载的那些驱动程序以及使用与当前 applet 或应用程序相同的类加载器显式加载的那些驱动程序中查找合适的驱动程序。
从 Java 2 SDK 标准版本 1.3 版开始,只有当已授予适当权限时设置日志流。通常这将使用工具 PolicyTool 完成,该工具可用于授予 permissionjava.sql.SQLPermission "setLog" 权限。