1. 先看看原生jdbc執行sql的步驟

// 在程式啟動的時候需要註冊一次mysql驅動,必須引入 mysql-connnector-java 的包
Class.forName("com.mysql.jdbc.Driver"); // 建立資料庫連線
Connection connection = DriverManager.getConnection("jdbcUrl", "userName", "password");
// 建立執行語句
Statement statment = connection.createStatement();
// 執行sql,返回結果集
ResultSet resultSet = statement.executeQuery("select * from table;");
// 最後,關閉連線
connection.close();

2. 為啥 Class.forName("com.mysql.jdbc.Driver") 載入一下類就能註冊驅動了?

看看 com.mysql.jdbc.Driver 類的程式碼 (版本5.1.47)

public class Driver  extends NonRegisteringDriver implements java.sql.Driver {
//
// Register ourselves with the DriverManager
//
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}

當類載入的時候會執行靜態程式碼塊,從上面可以看到,mysql的Driver靜態程式碼塊裡邊 將自己註冊到 DriverManager 裡了; 所以後續的 DriverManager#getConnection 的過程能使用到mysql的driver

3. DriverManager 在上述jdbc執行過程中的作用

3.1 DriverManager.registerDriver 顯式/主動註冊驅動

private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();  //驅動列表

public static synchronized void registerDriver(java.sql.Driver driver,
DriverAction da)
throws SQLException { /* Register the driver if it has not already been added to our list */
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);
}

registeredDrivers.addIfAbsent(new DriverInfo(driver, da)); 驅動的註冊過程就是將其加入到 一個 copyOnWrite 的列表裡;

3.2 DriverManager#getConnection 連線建立過程

    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());
}
}

4. DriverManager 自動載入驅動

除了主動呼叫 DriverManager#registerDriver() 註冊驅動外,DriverManager 其實還有兩種自動載入驅動的機制:

  • 系統變數 jdbc.drivers 定義的驅動, 然後內部也是通過 Class.forName 去註冊這些驅動類
  • 基於 ServiceLoader 的 service-provicer SPI 機制, 即資料庫驅動提供方在其類路徑下存在檔案 /META-INF/services/java.sql.Driver 指定其驅動的實現類,就能被DriverManager自動載入到;

具體邏輯: 在 DriverManager 的靜態程式碼塊裡,會執行 loadInitialDrivers() 一個自動驅動載入邏輯

 private static void loadInitialDrivers() {
String drivers;
try {
//1. 載入系統變數 jdbc.drivers 設定的冒號隔開的驅動
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
} AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() { // 2. service-provider 載入機制
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
}); println("DriverManager.initialize: jdbc.drivers = " + drivers); if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
// 內部也是利用 Class.forName 去載入 jdbc.drivers 定義的Driver類名稱
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}

總結

System.getProperty('jdbc.drivers") 這種方式個人覺得適合用於在應用初始化指令碼中指定

基於 ServiceLoader 的SPI方式,只要符合 spi 標準的驅動包引入了就會自動載入

我們在基於jdbc實現或封裝資料庫連線池、中介軟體的時候,就不需要再 Class.forName 的方式去顯示註冊驅動了,特別是當需要支援多種資料庫的時候,還得根據 jdbcUrl 去判斷目標 driverClassName 是啥, 如果要再支援新的關係型資料庫時候 還得再改程式碼

看看常用關係資料庫廠商的 spi 支援情況

  • mysql-connector-java 早在5.0.0(2005-12-22) 版本就添加了 META-INF/services/java.sql.Driver 檔案支援 service-provicer SPI; 現在一般用8.x的版本
  • pg connector 也在Version 42.2.13(2020-06-04) 之後支援

所以如果不用支援古董版本的驅動,基本可以放心直接 DriverManager#getConnection 去建立db連線了