1. 程式人生 > >Spring-JDBC 原始碼學習(3) —— DriverManager

Spring-JDBC 原始碼學習(3) —— DriverManager

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 包)。