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連線了