对JDBC驱动注册–DriverManager.registerDriver和Class.forName(driverClass)的理解
JDBC提供了独立于数据库的统一API,MySQL、Oracle等数据库公司都可以基于这个标准接口来进行开发。包括java.sql包下的Driver,Connection,Statement,ResultSet是JDBC提供的接口。而DriverManager是用于管理JDBC驱动的服务类,主要用于获取Connection对象(此类中全是静态方法)。
当我们查看API,在Driver接口中,明确要求:Driver接口是每个驱动程序类必须实现的接口。Java SQL 框架允许多个数据库驱动程序。每个驱动程序都应该提供一个实现 Driver 接口的类。并且明确:在加载某一 Driver 类时,它应该创建自己的实例并向 DriverManager 注册该实例。这意味着用户可以通过调用以下程序加载和注册一个驱动程序Class.forName(“foo.bah.Driver”)
下边重点分析 注册驱动的四种方式 :
第一种:
Driver driver = new Driver();//com.mysql.jdbc.Driver
DriverManager.registerDriver(driver);
也即为
DriverManager.registerDriver(new Driver());
第二种:
new Driver();
第三种:
Class.forName("com.mysql.cj.jdbc.Driver");
第四种:
可以直接不写
这四种注册方式有什么不同呢?
第一、二种方式,相对比较好理解,就是先创建数据库驱动,然后调用registerDriver()方法完成注册。
第三种方式是利用发射机制来完成的,直接看的话, 我们会想 Class.forName(driverClass) 只能帮助我们得到Driver的Class对象啊,为什么会帮我们完成注册了呢。从上边对Driver()的API的查阅,API要求:在加载某一 Driver 类时,它应该创建自己的实例并向 DriverManager 注册该实例。我们猜想是在类加载时,就自动完成了注册。
第四种方式:JDBC 4.0 Drivers 必须包括 META-INF/services/java.sql.Driver 文件。此文件包含 java.sql.Driver 的 JDBC 驱动程序实现的名称。 应用程序不再需要使用 Class.forName() 显式地加载 JDBC 驱动程序。
下边去具体看一下源码:
第一种方法,其JDK1.7下的DriverManger的registerDriver()方法:
public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {
/* Register the driver if it has not already been added to our list */
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);
// List of registered JDBC drivers
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<DriverInfo>();
从其源码,可以看到DriverManger将要注册的驱动程序信息封装到了DriverInfo中,然后放进了一个List中。在后边获得连接时会再用到。
第一种方法,其JDK1.8下的DriverManger的registerDriver()方法:
public static void registerDriver(Driver driver) throws SQLException {
registerDriver(driver, (DriverAction)null);
}
public static void registerDriver(Driver driver, DriverAction da) throws SQLException {
if (driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
println("registerDriver: " + driver);
} else {
throw new NullPointerException();
}
}
private static final CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList();
第三种方法:第三种方法是怎么通过只要获得Driver的Class对象就可以完成注册呢,下边看一下其com.mysql.jdbc.Driver的源码:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
// ~ Static fields/initializers
// ---------------------------------------------
//
// Register ourselves with the DriverManager
//
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
从上边可以看到,它是用静态代码块实现的。
根据类加载机制,当执行 Class.forName(driverClass) 获取其Class对象时, com.mysql.jdbc.Driver 就会被JVM加载,连接,并进行初始化,初始化就会执行静态代码块,也就会执行下边这句代码:java.sql.DriverManager.registerDriver(new Driver());
这就和第一种方式相同了。
实际上Class.forName(driverClass)中driverClass里面的内容是Driver类的全限定类名,如下图所示:
第四种方法:可以直接不写。
JDBC 4.0 Drivers 必须包括 META-INF/services/java.sql.Driver 文件。此文件包含 java.sql.Driver 的 JDBC 驱动程序实现的名称。
应用程序不再需要使用 Class.forName() 显式地加载 JDBC 驱动程序。
对于上边的四种驱动注册方法,我们一般采用第三种方法:
(1)第一、二种方式 Driver driver = new Driver()
其在内部也执行静态代码块,这相当于实例化了两个Driver对象;
(2)第一、二种方式 Driver driver = new Driver()
会产生对某一种数据库的依赖(会import驱动包),耦合性较高。
所以一般采用第三种方式。
JDBC连接数据库的步骤为:
案例代码:
package _01编写第一个JDBC程序;
import com.mysql.cj.jdbc.Driver;
import java.sql.*;
/**
* 编写第一个JDBC程序
*/
public class LoadDriverByThreeWays {
public static void main(String[] args) {
ResultSet resultSet = null;
Statement statement = null;
Connection connection = null;
try {
// 1. 加载驱动
//第一种方式
// DriverManager.registerDriver(new Driver());
//第二种方式
// new Driver();
//第三种方式
// Class.forName("com.mysql.cj.jdbc.Driver");
//第四种方式:直接不写
/*
* JDBC 4.0 Drivers 必须包括 META-INF/services/java.sql.Driver 文件。此文件包含 java.sql.Driver 的 JDBC 驱动程序实现的名称。
* 应用程序不再需要使用 Class.forName() 显式地加载 JDBC 驱动程序。
*
*/
// 2. 创建连接
/*
* JDBC连接数据库的url格式是:
* jdbc:子协议://subname
*
* JDBC连接MySQL数据库的url格式是:
* jdbc:mysql://主机名:端口号/数据库名 -----> jdbc:mysql://localhost:3306/db02
* 如果主机名:端口号是 localhost:3306 那么主机名:端口号可以省略 -----> jdbc:mysql:///db02
*/
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/powernode_jdbc", "root", "root");
// 3. 获取Statement语句对象
statement = connection.createStatement();
// 4. 执行SQL语句
String sql = "select id,name as xxx,age from student";
resultSet = statement.executeQuery(sql);
// 5. 解析ResultSet结果集
/*
* java.sql.SQLException: Before start of result set
* 出现该异常的原因是:目前ResultSet对象具有指向其当前数据行的光标,光标位于第一行数据之前
*
*/
while (resultSet.next()) {
/*
* resultSet中获取数据的方法getXXXX()都有两个重载方法。
* 一个是根据投影字段的索引获取数据;一个是根据投影字段的名称(别名)获取数据
*
* 注意:数据库索引从1开始
*/
// 根据投影字段的名称(别名)获取数据
String name = resultSet.getString("xxx");
/*
* 根据投影字段的索引获取数据 -- 不推荐使用
* 弊端:就是投影字段的顺序调整后,获取数据就就不对了
*/
// String name = resultSet.getString(2);
System.out.println("name= " + name);
}
} catch (Exception throwables) {
throwables.printStackTrace();
} finally {
// 6. 释放资源
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
resultSet = null;
}
if (statement != null) {
try {
statement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
statement = null;
}
if (connection != null) {
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
connection = null;
}
}
}
}