1. 程式人生 > >jdbc原始碼詳解(一):示例+Driver註冊流程

jdbc原始碼詳解(一):示例+Driver註冊流程

0x00 前言

自己一直說要某個開源專案的原始碼,但是一直沒有真正地好好開始,一是以為看原始碼其實不容易看懂,而是因為選擇猶豫,最後也敲定看哪個。

這次正式開始看jdbc的原始碼有兩個三個:一是因為《java程式設計思想》這本書快看完了,折騰一個多月的時間,裡面除了多執行緒和圖形程式設計這兩塊基本都看得差不多了;一個是因為《設計模式之禪》這本書看了一半左右,裡面的設計模式自己大致都明白是什麼隱私,但是印象不深刻,需要有一個穩固的過程;一個是因為jdbc相對來說比較熟悉,加上以後肯定會常和資料庫打交道,因此就決定看了。

在分析原始碼的時候,我會先自己理清思路,然後再回頭來總結,總結的過程儘量地變成一個探索的過程,以此來梳理自己看原始碼的方法論。

0x01 閱讀環境

  • ide:idea15
  • jdk:7
  • mysql-connector-java:5.1.34
  • h2:1.4.187

這次選了mysql和h2兩款資料庫的jdbc程式來分析,mysql是因為這個是最常用的,以後也會經常和它打交道,h2是因為它是java寫的資料庫,以後準備看它的原始碼,現在先提前瞭解一下。

0x02 jdbc示例

下面是一個最基本的jdbc示例,通過這個例子,後面我會詳細地介紹整個流程。

第一個jdbc程式

這是一個最基本的jdbc連線程式,我省掉了異常處理。

public class JDBCTest {
    public static
void main(String[] args) { Connection conn = null; Statement stmt = null; try{ //STEP 1: Register JDBC driver Class.forName("com.mysql.jdbc.Driver"); //STEP 2: Open a connection conn = DriverManager.getConnection("jdbc:mysql://192.168.108.145/test"
, "root", "root"); //STEP 3: Execute a query stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("select * from userinfo limit 1"); //STEP 4: Get results while(rs.next()){ System.out.println(rs.getString("id")); } rs.close(); }catch(SQLException se){ ...... }//end try } }

maven依賴

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.34</version>
</dependency>

jdbc流程

從例子中可以看出,一個基本的jdbc的程式需要四步:

  1. 註冊jdbc的driver
  2. 獲取一個連線
  3. 進行查詢
  4. 獲取結果

0x03 Driver註冊流程分析

Class.forName是個什麼東西?

我一直很好奇jdbc是怎麼註冊的,但是第一次看原始碼的時候對什麼反射之類的概念的基本都弄不明白,因此卡在這第一行程式碼就卡了好久,結果好幾次都沒有進行下去。

Class.forName("com.mysql.jdbc.Driver");

具體的這一行的程式碼語法方面我還是不解釋了,搞java的,Class類的功能還是需要知道的,不懂的還是需要再看看書了。

程式碼先繼續分析,然後扭頭來講這句的功能。

DriverManager類

The basic service for managing a set of JDBC drivers.

這樣說吧,DriverManager是管理一個jdbc driver的基礎服務。我們順著程式碼看一下getConnection()方法,在原始碼中有著四個:

  1. getConnection(String url,String user, String password)
  2. getConnection(String url,java.util.Properties info)
  3. getConnection(String url)
  4. getConnection(String url, java.util.Properties info, ClassLoader callerCL)

其實前三個方法最後都呼叫了最後一個,舉個栗子看一下,在我們剛才寫的程式中是通過呼叫第一個方法來傳遞引數的,這個方法做了一些拼接後也是呼叫了最後一個方法。

    public static Connection getConnection(String url, String user, String password) throws SQLException {
        java.util.Properties info = new java.util.Properties();
        ClassLoader callerCL = DriverManager.getCallerClassLoader();
        if (user != null) {
            info.put("user", user);
        }
        if (password != null) {
            info.put("password", password);
        }
        //調了這個getConnection
        return (getConnection(url, info, callerCL));
    }

方法:getConnection(String url, java.util.Properties info, ClassLoader callerCL)

在這個方法中可以看出,當執行getConnection後,會先鎖住DriverManager,然後開始registeredDrivers中所有的已註冊驅動,也就是說DriverManager中註冊不止一個Driver驅動,如果一些系統中有多個驅動的時候,然後它迴圈掃描所有的驅動程式,然後通過connect方法獲取是否來呼叫。具體如何來進行connect後面再說,這次來詳細分析Driver的註冊過程。

    //registeredDrivers存放的已經註冊的驅動。
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<DriverInfo>();

    ......

    private static Connection getConnection(
        String url, java.util.Properties info, ClassLoader callerCL) throws SQLException {
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        //先鎖住該類
        synchronized (DriverManager.class) {
            if (callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();
            }
        }
        //判斷是否為空,把url設定成null試試,就丟擲這個異常
        if(url == null) {
            throw new SQLException("The url cannot be null", "08001");
        }
        ......
        for(DriverInfo aDriver : registeredDrivers) {
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    //關鍵在這一句中,在這裡嘗試連結具體的url
                    Connection con = aDriver.driver.connect(url, info);
                    if (con != null) {
                        // Success!
                        println("getConnection returning " + aDriver.driver.getClass().getName());
                        return (con);
                    }
                } catch (SQLException ex) {
                    ......
                }
            } 
            ......
        }
    ......
    }

我們先看一下Driver的註冊過程。registeredDrivers是一個CopyOnWriteArrayList,它是執行緒安全的ArrayList,在java的concurrent包中,這是在java7後改進的,之前的版本用的不是它,具體用的是什麼資料結構忘掉了。這裡的驅動註冊,其實就是把具體的Driver給新增到了registeredDrivers裡面。下面的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);
    }

哪些驅動會被註冊?

那麼在哪個地方呼叫了registerDriver方法呢?這個需要繼續往下看?在具體看某個特定的驅動是如何載入之前,先看一下到底有哪些驅動會被載入,它是在什麼時候被載入。執行一下下面的程式碼。

//Class.forName("com.mysql.jdbc.Driver");
Enumeration<Driver> enumeration = DriverManager.getDrivers();
while (enumeration.hasMoreElements()) {
    System.out.println(enumeration.nextElement());
}

可以看出,在預設的時候,是有一個驅動已經被載入了的,暫時不清楚它的作用。

sun.jdbc.odbc.JdbcOdbcDriver@73ae9565

然後在把Class.forName的註釋去掉,再執行,就可以看到多了mysql的兩個驅動,我們用到的是第三個。可以看出來,Class.forName執行後的確是把驅動載入進來了。

sun.jdbc.odbc.JdbcOdbcDriver@1dd1702e
com.mysql.fabric.jdbc.FabricMySQLDriver@5aa6343d
com.mysql.jdbc.Driver@7e14feea

mysql jdbc的Driver

前面畢竟是通過結果來推測而來,下面我們進入mysql的Driver中看一下Driver究竟是如何註冊的。

可以看出,在Driver這裡的靜態程式碼塊裡,呼叫了DriverManager的registerDriver方法來進行註冊。也就是說,當我們在程式中呼叫Class.forName("com.mysql.jdbc.Driver")的後,com.mysql.jdbc.Driver類就會被載入,同時也在靜態程式碼塊中完成了向DriverManager的註冊。這樣就能回答前面提出的兩個問題了吧。

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!");
        }
    }

    //Construct a new driver and register it with DriverManager
    public Driver() throws SQLException {
        // Required for Class.forName().newInstance()
    }
}

h2 jdbc的Driver

看完mysql的順便看一下h2的是不是也是這個樣子的,多看一下又不會懷孕。可以看出來,它和mysql基本一樣,都在靜態程式碼塊中通過呼叫DriverManager的registerDriver方法來進行註冊。

    private static final Driver INSTANCE = new Driver();
    private static volatile boolean registered;
    static {
        load();
    }   
    public static synchronized Driver load() {
        try {
            if (!registered) {
                registered = true;
                DriverManager.registerDriver(INSTANCE);
            }
        } catch (SQLException e) {
            DbException.traceThrowable(e);
        }
        return INSTANCE;
    }

NonRegisteringDriver

NonRegisteringDriver是一個挺重要的類,後面會詳細的分析它,這次就先看一下官方的註釋。描述的很好很清晰。

The Java SQL framework allows for multiple database drivers. Each driver should supply a class that implements the Driver interface

The DriverManager will try to load as many drivers as it can find and then for any given connection request, it will ask each driver in turn to try to connect to the target URL.

It is strongly recommended that each Driver class should be small and standalone so that the Driver class can be loaded and queried without bringing in vastquantities of supporting code.

When a Driver class is loaded, it should create an instance of itself and register it with the DriverManager. This means that a user can load and register a

driver by doing Class.forName(“foo.bah.Driver”)

0x04 總結

現在從頭再梳理一下jdbc的註冊過程。

第一步:

先看一下自己寫的程式碼:

Class.forName("com.mysql.jdbc.Driver");

第二步:

我們寫了這行程式碼,但是從表面上來看,其實什麼都沒有做。然後看一下com.mysql.jdbc.Driver這個類。

com.mysql.jdbc.Driver中的下面這段程式碼中進行了驅動的註冊。

java.sql.DriverManager.registerDriver(new Driver());

第三步:

然後在java.sql.DriverManager.registerDriver方法中,mysql的jdbc driver就被註冊到registeredDrivers這個特殊的list中。

第四步:

然後在當需要獲取connection的時候,在DriverManager中的一個getConnection中,就會從registeredDrivers中來獲取已經註冊的driver。

2016-06-07 17:00:00 hzct