DriverManager
上面提到 DataSource 獲取連接(Connection) 的操作實質上將委託具體的 Driver 來提供 Connection 。有兩種不同的方式,包括經由 DriverManager 遍歷所有處於管理下的 Driver 嘗試獲取連接,或者在 DataSource 實例中直接聲明一個特定的 Driver 來獲取連接。
對於獲取連接的具體操作,挖坑-待填。只描述簡單的數據庫供應商提供的 Driver 如何與 java 相聯繫。
在 DriverManager 中註冊 Driver 實例
通常在與數據庫交互邏輯的 Java 代碼中,都會有 Class.forName("com.mysql.jdbc.Driver")
(此處以 MySQL 提供的 mysql-connector-java-XXX.jar 爲例,下同)的代碼塊,加載指定的 com.mysql.jdbc.Driver 爲 java.lang.Class 類。
當然,在 JDBC 4.0 標準下,可以不必再顯示聲明 Class.forName("")
語句,Driver 也同樣會在 DriverManager 初始化時自動註冊。
// Class 類中對於 forName(String className) 的方法
// 作用爲返回一個 java.lang.Class 實例。
public static Class<?> forName(String className) throws ClassNotFoundException {...}
同時, JVM 在加載類的過程中會執行類中的 static 代碼塊。下述 row 10 ~ 16 的代碼片段將被執行。唯一的邏輯就是 new 一個 com.mysql.jdbc.Driver 實例,並將實例註冊(registerDriver) 到 java.sql.DriverManager 中。
package com.mysql.jdbc;
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!");
}
}
// ~ Constructors
// -----------------------------------------------------------
/**
* Construct a new driver and register it with DriverManager
*
* @throws SQLException
* if a database error occurs.
*/
public Driver() throws SQLException {
// Required for Class.forName().newInstance()
}
}
下面再來看一下 DriverManager 中的 registerDriver() 方法。
public class DriverManager {
// DriverManager 維護一個線程安全的 Driver 列表
// 此處的 DriverInfo 裏面即包裝了 Driver
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers =
new CopyOnWriteArrayList<>();
// 在 DriverManager 中註冊 Driver
public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {
registerDriver(driver, null);
}
public static synchronized void registerDriver(java.sql.Driver driver,
DriverAction da)
throws SQLException {
/* 如果當前 Driver 不在列表中,即添加到列表。 */
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
}
通過 DriverManager 獲取連接(Connection)
上一節有提到過可以通過 DriverManager 來遍歷獲取連接,也可以直接聲明具體 Driver 並獲取連接。下面代碼展示的是通過 DriverManager 獲取連接的操作。 哈哈哈,反正最後都是由具體驅動實現獲取連接。
public class DriverManager {
// 獲取連接的 public 接口 (1)
public static Connection getConnection(String url,
java.util.Properties info) throws SQLException {
return (getConnection(url, info, Reflection.getCallerClass()));
}
// 獲取連接的 public 接口 (2)
public static Connection getConnection(String url,
String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}
return (getConnection(url, info, Reflection.getCallerClass()));
}
// 獲取連接的 public 接口 (3)
public static Connection getConnection(String url)
throws SQLException {
java.util.Properties info = new java.util.Properties();
return (getConnection(url, info, Reflection.getCallerClass()));
}
// 獲取連接的內部邏輯實現
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller)
throws SQLException {
/*
* When callerCl is null, we should check the application's
* (which is invoking this class indirectly)
* classloader, so that the JDBC driver class outside rt.jar
* can be loaded from here.
*/
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
// url 是定位 DBMS 最重要的參數,不能爲空
if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}
println("DriverManager.getConnection(\"" + url + "\")");
// 遍歷所有註冊的 Driver ,並都嘗試獲取連接(Connection)
SQLException reason = null;
for(DriverInfo aDriver : registeredDrivers) {
// 判斷註冊的 Driver 是否由 ClassLoader callerCL 加載,不是則跳過
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
// 獲取連接,:) 還是由 driver 實例自行提供
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 (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");
}
}
簡單的提一嘴,Connection 仍然只是一個針對 Java -> DB Server 的上層接口,如果想要更深層次地瞭解 Connection 與 DB Server 的交互,可以嘗試去看一下 com.mysql.jdbc.MysqlIO 類,MySQL 實現的 JDBC4Connection 類也是在使用該類來實現對 DB Server 交互。(哈哈,只看過 MySQL 提供的 Driver 包)。