深入理解jdbc系列2

  一般情況下,在應(yīng)用程序中進(jìn)行數(shù)據(jù)庫連接,調(diào)用JDBC接口,首先要將特定廠商的JDBC驅(qū)動實現(xiàn)加載到系統(tǒng)內(nèi)存中,然后供系統(tǒng)使用。基本結(jié)構(gòu)圖如下:
image.png

驅(qū)動加載入內(nèi)存的過程

這里所謂的驅(qū)動,其實就是實現(xiàn)了java.sql.Driver接口的類。如oracle的驅(qū)動類是 oracle.jdbc.driver.OracleDriver.class(此類可以在oracle提供的JDBC jar包中找到),此類實現(xiàn)了java.sql.Driver接口。

由于驅(qū)動本質(zhì)上還是一個class,將驅(qū)動加載到內(nèi)存和加載普通的class原理是一樣的:使用Class.forName("driverName")。以下是將常用的數(shù)據(jù)庫驅(qū)動加載到內(nèi)存中的代碼:

            //加載Oracle數(shù)據(jù)庫驅(qū)動
            Class.forName("oracle.jdbc.driver.OracleDriver");
            
            //加載SQL Server數(shù)據(jù)庫驅(qū)動
            Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
            
            //加載MySQL 數(shù)據(jù)庫驅(qū)動
            Class.forName("com.mysql.jdbc.Driver");

注意:Class.forName()將對應(yīng)的驅(qū)動類加載到內(nèi)存中,然后執(zhí)行內(nèi)存中的static靜態(tài)代碼段,代碼段中,會創(chuàng)建一個驅(qū)動Driver的實例,放入DriverManager中,供DriverManager使用。

 例如,在使用Class.forName() 加載oracle的驅(qū)動oracle.jdbc.driver.OracleDriver時,會執(zhí)行OracleDriver中的靜態(tài)代碼段,創(chuàng)建一個OracleDriver實例,然后調(diào)用DriverManager.registerDriver()注冊:
    static {
        Timestamp localTimestamp = Timestamp.valueOf("2000-01-01 00:00:00.0");
        try {
            if (defaultDriver == null) {
                //創(chuàng)建一個OracleDriver實例,然后注冊到DriverManager中
                                defaultDriver = new OracleDriver();
                DriverManager.registerDriver(defaultDriver);
            }
 
        } catch (RuntimeException localRuntimeException) {
        } catch (SQLException localSQLException) {
        }

Driver的功能

  java.sql.Driver接口規(guī)定了Driver應(yīng)該具有以下功能:
image.png

其中:

acceptsURL(String url) 方法用來測試對指定的url,該驅(qū)動能否打開這個url連接。driver對自己能夠連接的url會制定自己的協(xié)議,只有符合自己的協(xié)議形式的url才認(rèn)為自己能夠打開這個url,如果能夠打開,返回true,反之,返回false;

例如:oracle定義的自己的url協(xié)議如下:

jdbc:oracle:thin:@//<host>:<port>/ServiceName

jdbc:oracle:thin:@<host>:<port>:<SID>

oracle自己的acceptsURL(String url)方法如下:

    public boolean acceptsURL(String paramString) {
        if (paramString.startsWith("jdbc:oracle:")) {
            return (oracleDriverExtensionTypeFromURL(paramString) > -2);
        }
 
        return false;
    }
 
    private int oracleDriverExtensionTypeFromURL(String paramString) {
        int i = paramString.indexOf(58) + 1;
 
        if (i == 0) {
            return -2;
        }
        int j = paramString.indexOf(58, i);
 
        if (j == -1) {
            return -2;
        }
        if (!(paramString.regionMatches(true, i, "oracle", 0, j - i))) {
            return -2;
        }
        ++j;
 
        int k = paramString.indexOf(58, j);
 
        if (k == -1) {
            return -3;
        }
        String str = paramString.substring(j, k);
 
        if (str.equals("thin")) {
            return 0;
        }
        if ((str.equals("oci8")) || (str.equals("oci"))) {
            return 2;
        }
 
        return -3;
    }
 

由上可知oracle定義了自己應(yīng)該接收什么類型的URL,自己能打開什么類型的URL連接(注意:這里acceptsURL(url)只會校驗url是否符合協(xié)議,不會嘗試連接判斷url是否有效)

connect(String url,Properties info)方法,創(chuàng)建Connection對象,用來和數(shù)據(jù)庫的數(shù)據(jù)操作和交互,而Connection則是真正數(shù)據(jù)庫操作的開始(在此方法中,沒有規(guī)定是否要進(jìn)行acceptsURL()進(jìn)行校驗)。

手動加載驅(qū)動 Driver 并實例化進(jìn)行數(shù)據(jù)庫操作的例子
    public static void driverTest(){
        try {
            //1.加載oracle驅(qū)動類,并實例化
            Driver driver = (Driver) Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();
 
            //2.判定指定的URL oracle驅(qū)動能否接受(符合oracle協(xié)議規(guī)則)
            boolean flag = driver.acceptsURL("jdbc:oracle:thin:@127.0.0.1:1521:xe");
            //標(biāo)準(zhǔn)協(xié)議測試
            boolean standardFlag1 = driver.acceptsURL("jdbc:oracle:thin:@//<host>:<port>/ServiceName");
            boolean standardFlag2 = driver.acceptsURL("jdbc:oracle:thin:@<host>:<port>:<SID>");
            System.out.println("協(xié)議測試:"+flag+"\t"+standardFlag1+"\t"+standardFlag2);
            
            //3.創(chuàng)建真實的數(shù)據(jù)庫連接:
            String  url = "jdbc:oracle:thin:@127.0.0.1:1521:xe";
            Properties props = new Properties();
            props.put("user", "louluan");
            props.put("password", "123456");
            Connection connection = driver.connect(url, props);
            //connection 對象用于數(shù)據(jù)庫交互,代碼省略。。。。。
            
        } catch (Exception e) {
            System.out.println("加載Oracle類失敗!");
            e.printStackTrace();
        } finally{
            
        }
    }
   上述的手動加載Driver并且獲取連接的過程稍顯笨拙:如果現(xiàn)在我們加載進(jìn)來了多個驅(qū)動Driver,那么手動創(chuàng)建Driver實例,并根據(jù)URL進(jìn)行創(chuàng)建連接就會顯得代碼雜亂無章,并且還容易出錯,并且不方便管理。JDBC中提供了一個DriverManager角色,用來管理這些驅(qū)動Driver。
DriverManager角色

事實上,一般我們操作Driver,獲取Connection對象都是交給DriverManager統(tǒng)一管理的。DriverManger可以注冊和刪除加載的驅(qū)動程序,可以根據(jù)給定的url獲取符合url協(xié)議的驅(qū)動Driver或者是建立Conenction連接,進(jìn)行數(shù)據(jù)庫交互。

image.png

以下是DriverManager的關(guān)鍵方法摘要:


image.png
DriverManager 內(nèi)部持有這些注冊進(jìn)來的驅(qū)動 Driver,由于這些驅(qū)動都是 java.sql.Driver 類型,那么怎樣才能獲得指定廠商的驅(qū)動Driver呢?答案就在于:

  java.sql.Driver接口規(guī)定了廠商實現(xiàn)該接口,并且定義自己的URL協(xié)議。廠商們實現(xiàn)的Driver接口通過acceptsURL(String url)來判斷此url是否符合自己的協(xié)議,如果符合自己的協(xié)議,則可以使用本驅(qū)動進(jìn)行數(shù)據(jù)庫連接操作,查詢驅(qū)動程序是否認(rèn)為它可以打開到給定 URL 的連接。
使用DriverManager獲取指定Driver

對于驅(qū)動加載后,如何獲取指定的驅(qū)動程序呢?這里,DriverManager的靜態(tài)方法getDriver(String url)可以通過傳遞給的URL,返回可以打開此URL連接的Driver。
比如,我想獲取oracle的數(shù)據(jù)庫驅(qū)動,只需要傳遞形如jdbc:oracle:thin:@<host>:<port>:<SID>或者jdbc:oracle:thin:@//<host>:<port>/ServiceName的參數(shù)給DriverManager.getDriver(String url)即可:

Driver oracleDriver =DriverManager.getDriver("jdbc:oracle:thin:@<host>:<port>:<SID>");

實際上,DriverManager.getDriver(String url)方法是根據(jù)傳遞過來的URL,遍歷它維護(hù)的驅(qū)動Driver,依次調(diào)用驅(qū)動的Driver的acceptsURL(url),如果返回acceptsURL(url)返回true,則返回對應(yīng)的Driver:

    public static Driver getDriver(String paramString) throws SQLException {
    
        //省略部分代碼。。。。
        Iterator localIterator = registeredDrivers.iterator();
        //遍歷注冊的驅(qū)動
        while (localIterator.hasNext()) {
            DriverInfo localDriverInfo = (DriverInfo) localIterator.next();
            if (isDriverAllowed(localDriverInfo.driver, localClass))
                try {
                    //如果accepsURL() 為true,返回對應(yīng)的driver
                    if (localDriverInfo.driver.acceptsURL(paramString)) {
                        //返回對應(yīng)的driver
                        return localDriverInfo.driver;
                    }
                } catch (SQLException localSQLException) {
                }
            else
                println("    skipping: "+ localDriverInfo.driver.getClass().getName());
        }
        throw new SQLException("No suitable driver", "08001");
        //-----省略部分代碼
    }
使用DriverManager注冊和取消注冊驅(qū)動Driver

在本文開始的 驅(qū)動加載的過程中,討論了當(dāng)使用Class.forName("driverName")加載驅(qū)動的時候,會向DriverManager中注冊一個Driver實例。以下代碼將驗證此說法:

    public static void defaultDriver(){
        try {
            
            String url = "jdbc:oracle:thin:@127.0.0.1:1521:xe";
            
            //1.將Driver加載到內(nèi)存中,然后執(zhí)行其static靜態(tài)代碼,創(chuàng)建一個OracleDriver實例注冊到DriverManager中
            Class.forName("oracle.jdbc.driver.OracleDriver");
            //取出對應(yīng)的oracle 驅(qū)動Driver
            Driver driver  = DriverManager.getDriver(url);
            System.out.println("加載類后,獲取Driver對象:"+driver);
            
            //將driver從DriverManager中注銷掉
            DriverManager.deregisterDriver(driver);
            //重新通過url從DriverManager中取Driver
            driver  = DriverManager.getDriver(url);
            System.out.println(driver);
            
        } catch (Exception e) {
            System.out.println("加載Oracle類失敗!");
            e.printStackTrace();
        } finally{
            
        }
    }

以上代碼主要分以下幾步:

1. 首先是將  oracle.jdbc.driver.OracleDriver加載到內(nèi)存中;

 2.  然后便調(diào)用DriverManager.getDriver()去取Driver實例;

3.  將driver實例從DriverManager中注銷掉;

4.嘗試再取 對應(yīng)url的Driver實例;

上述代碼執(zhí)行的結(jié)果如下:

image.png
  從執(zhí)行結(jié)果看,正好能夠驗證以上論述:當(dāng)?shù)谒牟皆俅潍@取對應(yīng)url的 Driver 實例時,由于已經(jīng)被注銷掉了,找不到適當(dāng)?shù)尿?qū)動Driver,拋出了 "Not suitable driver" 的異常。

 將上述的例子稍作變化,在注銷掉了靜態(tài)塊創(chuàng)建的driver后,往DriverManager注冊一個自己創(chuàng)建的Driver對象實例(具體步驟請看注釋):
    public static void defaultDriver(){
        try {
            
            String url = "jdbc:oracle:thin:@127.0.0.1:1521:xe";
            
            //1.將Driver加載到內(nèi)存中,然后執(zhí)行其static靜態(tài)代碼,創(chuàng)建一個OracleDriver實例注冊到DriverManager中
            Driver dd = (Driver)Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();
            //2.取出對應(yīng)的oracle 驅(qū)動Driver
            Driver driver  = DriverManager.getDriver(url);
            System.out.println("加載類后,獲取Driver對象:"+driver);
            
            //3. 將driver從DriverManager中注銷掉
            DriverManager.deregisterDriver(driver);
            
            //4.此時DriverManager中已經(jīng)沒有了驅(qū)動Driver實例,將創(chuàng)建的dd注冊到DriverManager中
            DriverManager.registerDriver(dd);
            
            //5.重新通過url從DriverManager中取Driver
            driver  = DriverManager.getDriver(url);
                       
                        System.out.println("注銷掉靜態(tài)創(chuàng)建的Driver后,重新注冊的Driver:    "+driver);
            System.out.println("driver和dd是否是同一對象:" +(driver==dd));
        } catch (Exception e) {
            System.out.println("加載Oracle類失敗!");
            e.printStackTrace();
        } finally{
            
        }
    }

以下代碼運行的結(jié)果:


image.png

以上代碼先創(chuàng)建了一個Driver對象,在注銷了DriverManager中由加載驅(qū)動過程中靜態(tài)創(chuàng)建驅(qū)動之后,注冊到系統(tǒng)中,現(xiàn)在DriverManager中對應(yīng)url返回的Driver 即是在代碼中創(chuàng)建的Driver對象。

使用DriverManager創(chuàng)建 Connection 連接對象
  創(chuàng)建 Connection 連接對象,可以使用驅(qū)動Driver的 connect(url,props),也可以使用 DriverManager 提供的getConnection()方法,此方法通過url自動匹配對應(yīng)的驅(qū)動Driver實例,然后調(diào)用對應(yīng)的connect方法返回Connection對象實例。
Driver driver  = DriverManager.getDriver(url);
            Connection connection = driver.connect(url, props);

上述代碼等價于:

            Class.forName("oracle.jdbc.driver.OracleDriver");
            Connection connection = DriverManager.getConnection(url, props);

jdbc.drivers
DriverManager 作為 Driver 的管理器,它在第一次被使用的過程中(即在代碼中第一次用到的時候),它會被加載到內(nèi)存中,然后執(zhí)行其定義的static靜態(tài)代碼段,在靜態(tài)代碼段中,有一個 loadInitialDrivers() 靜態(tài)方法,用于加載配置在jdbc.drivers 系統(tǒng)屬性內(nèi)的驅(qū)動Driver,配置在jdbc.drivers 中的驅(qū)動driver將會首先被加載:

    static {
        loadInitialDrivers();//加載配置在jdbc.drivers系統(tǒng)變量中的驅(qū)動driver
        println("JDBC DriverManager initialized");
        SET_LOG_PERMISSION = new SQLPermission("setLog");
    }
    private static void loadInitialDrivers() {
        String str1;
        try {
            str1 = (String) AccessController
                    .doPrivileged(new PrivilegedAction() {
                        public String run() {
                            return System.getProperty("jdbc.drivers");//返回jdbc.drivers值
                        }
                    });
        } catch (Exception localException1) {
            str1 = null;
        }
 
                //省略部分代碼......
        if ((str1 == null) || (str1.equals("")))
            return;
        String[] arrayOfString1 = str1.split(":");
        println("number of Drivers:" + arrayOfString1.length);
        for (String str2 : arrayOfString1)
            try {
                //Class.forName加載對應(yīng)的driver
                Class.forName(str2, true, ClassLoader.getSystemClassLoader());
            } catch (Exception localException2) {
                println("DriverManager.Initialize: load failed: "
                        + localException2);
            }
    }

以下是通過jdbc.drivers加載驅(qū)動driver的方式:

            String url = "jdbc:oracle:thin:@127.0.0.1:1521:xe";
            //設(shè)置值系統(tǒng)變量jdbc.drivers
            System.setProperty("jdbc.drivers", "oracle.jdbc.driver.OracleDriver");
            //2.通過特定的url獲取driver
            Driver driver = DriverManager.getDriver(url);
            //打印是否存在
            System.out.println(driver);

請記住:一定要在第一次使用DriverManager之前設(shè)置jdbc.drivers,因為DriverManager中的static靜態(tài)代碼段只會被執(zhí)行一次!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 一.介紹 JDBC(Java DataBase Connectivity,java數(shù)據(jù)庫連接)是一種用于執(zhí)行SQL...
    走著別浪閱讀 348評論 0 2
  • Connection對象表示通過啟用JDBC技術(shù)的驅(qū)動程序與數(shù)據(jù)源的連接。 數(shù)據(jù)源可以是DBMS,傳統(tǒng)文件系統(tǒng)或其...
    zlb閱讀 1,558評論 0 1
  • 本文主要內(nèi)容 1、JDBC 2、DBUtils 01JDBC概念和數(shù)據(jù)庫驅(qū)動程序 A: JDBC概念和數(shù)據(jù)庫驅(qū)動程...
    勝浩_ae28閱讀 409評論 0 0
  • 1. 簡介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL、存儲過程以及高級映射的優(yōu)秀的...
    笨鳥慢飛閱讀 5,572評論 0 4
  • 姓名:張漢超 公司:東莞耀升機電有限公司 組別:4月25-27日六項精進(jìn)245期學(xué)員 【日精進(jìn)打卡第129天】 【...
    張漢超閱讀 251評論 0 0