`
j小虫
  • 浏览: 18827 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

JDBC分析驱动加载全过程

    博客分类:
  • java
阅读更多

  首先介绍一下,JDBC框架做到了热插拔。

一.Driver接口

//Driver.java

package java.sql;

public interface Driver {

    Connection connect(String url, java.util.Properties info) throws SQLException;

    boolean acceptsURL(String url) throws SQLException;

    DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info) throws SQLException;

    int getMajorVersion();                                             // 返回驱动的主版本号

    int getMinorVersion();                                             // 返回驱动的次版本号

    boolean jdbcCompliant();                              // 是否兼容于 JDBC 标准

  这个就是java的Driver接口,非常简单,任何一个数据库驱动提供商都需要实现该接口,而DriverManager等类中使用Driver时都是直接使用Driver接口,这样就做到了实现与逻辑相分离。也就是OO中提到的依赖反转,实现与逻辑可以由不同的专业人员去实现。

JDBC Framework 中允许加载多个数据库的驱动!相应地,一般建议数据库提供商的驱动必须小一点,从而保证在加载多个驱动后不会占用太多的内存。

二.DriverManager类

  DriverManager可以建立数据库连接,我们把它按功能分为3部:1. 初始化; 2. 驱动的注册、查询、取消注册; 3. 建立连接; 4. 日志相关。

  先看其中代码,有一个类

// DriverInfo is a package-private support class.
class DriverInfo {
    Driver         driver;
    Class          driverClass;
    String         driverClassName;

    public String toString() {
    return ("driver[className=" + driverClassName + "," + driver + "]");
    }
}

  他包含了Driver的相关信息,是一个辅助类。

 

  再看DriverManager的构造

  /* Prevent the DriverManager class from being instantiated. */
    private DriverManager(){}

  是一个私有的构造,说明它不能被实例,只能用静态方法,是一个工具类,创建连接的工具类。

 

1.初始化

  private static boolean initialized = false;                                              // 是否初始化的标记,默认当然是否了

  static void initialize() {
        if (initialized) {
            return;                                                     //初始化过就直接return
        }
        initialized = true;                                         //没初始化就改成已初始化标记
        loadInitialDrivers();                                     //加载驱动集
        println("JDBC DriverManager initialized");
    }

 

private static boolean initialized = false;                                              // 是否初始化的标记,默认当然是否了

 

// 真正的初始化方法

    static void initialize() {

        if (initialized) {    return;     }                                   // 已经初始化就返回!(初始化了就算了)

        initialized = true;                                                               // 设置此标识符,表示已经完成初始化工作

        loadInitialDrivers();                                                           // 初始化工作主要是完成所有驱动的加载

        println("JDBC DriverManager initialized");

}

 

// 初始化方法中完成加载所有系统提供的驱动的方法

    private static void loadInitialDrivers() {

        String drivers;

        try {

         drivers = (String) java.security.AccessController.doPrivileged(

              new sun.security.action.GetPropertyAction("jdbc.drivers"));

                                 // 得到系统属性 "jdbc.drivers" 对应的驱动的驱动名(这可是需要许可的哦!)

        } catch (Exception ex) {

            drivers = null;

        }       

        Iterator ps = Service.providers(java.sql.Driver.class);         // 从系统服务中加载驱动

        while (ps.hasNext()) {                                                                        // 加载这些驱动,从而实例化它们

            ps.next();

        } 

        println("DriverManager.initialize: jdbc.drivers = " + drivers);

        if (drivers == null) {    return;       }                                       // 系统属性未指定驱动则返回

        while (drivers.length() != 0) {                                                              // 循环过程,讲解见下面

             int x = drivers.indexOf(':');

            String driver;

            if (x < 0) {

                driver = drivers;

                drivers = "";

            } else {

                 driver = drivers.substring(0, x);

                drivers = drivers.substring(x+1);

            }

            if (driver.length() == 0) {     continue;      }

            try {

                println("DriverManager.Initialize: loading " + driver);

                 Class.forName(driver, true, ClassLoader.getSystemClassLoader());       

                                                   // 加载这些驱动,下篇会讲解其细节

            } catch (Exception ex) {

                println("DriverManager.Initialize: load failed: " + ex);

            }

        }//end of while

                // 系统属性 "jdbc.drivers" 可能有多个数据库驱动,这些驱动的名字是以“ : ”分隔开的,

                // 上面的过程就是将此以“ : ”分隔的驱动,依次遍列,然后调用 Class.forName 依次加载

}

2.驱动的注册,查询,取消注册

   public static synchronized void registerDriver(java.sql.Driver driver)
    throws SQLException {
    if (!initialized) {
        initialize();                                   //未初始化先初始化
    }
     
    DriverInfo di = new DriverInfo();

    di.driver = driver;
    di.driverClass = driver.getClass();
    di.driverClassName = di.driverClass.getName();

    // Not Required -- drivers.addElement(di);

    writeDrivers.addElement(di);                       
    println("registerDriver: " + di);
   
    /* update the read copy of drivers vector */
    readDrivers = (java.util.Vector) writeDrivers.clone();

    }

 

    public static synchronized Driver getDriver(String url) throws SQLException {

        println("DriverManager.getDriver( "" + url + " ")");

        if (!initialized) {      initialize();        }                 // 同样必须先初始化

         // 本地方法,得到调用此方法的类加载器

                ClassLoader callerCL = DriverManager.getCallerClassLoader();            

        // 遍列所有的驱动信息,返回能理解此 URL 的驱动

        for (int i = 0; i < drivers.size(); i++) {                                                  // 遍列驱动信息的聚集

             DriverInfo di = (DriverInfo)drivers.elementAt(i);

                     // 调用者在没有权限加载此驱动时会忽略此驱动

            if ( getCallerClass(callerCL, di.driverClassName ) != di.driverClass ) {

                println("    skipping: " + di);

                continue;

             }

            try {

                println("    trying " + di);

                                   if (di.driver.acceptsURL(url)) {                                // 驱动能理解此 URL 时,返回此驱动

                    println("getDriver returning " + di);

                    return (di.driver);

                }

            } catch (SQLException ex) {

                                 // Drop through and try the next driver.

            }

        }

        println("getDriver: no suitable driver");

        throw new SQLException("No suitable driver", "08001");

    }

 

// DriverManager 中取消注册某个驱动。 Applet 仅仅能够取消注册从它的类加载器加载的驱动

    public static synchronized void deregisterDriver(Driver driver) throws SQLException {

              ClassLoader callerCL = DriverManager.getCallerClassLoader();

              println("DriverManager.deregisterDriver: " + driver);     

              int i;

              DriverInfo di = null;

              for (i = 0; i < drivers.size(); i++) {

                   di = (DriverInfo)drivers.elementAt(i);

                  if (di.driver == driver) { break;    }                // 找到了某个驱动则返回,同时返回 i

              }

         if (i >= drivers.size()) {                                                         // 全部遍列完,度没有找到驱动则返回

             println("    couldn't find driver to unload");

             return;

         }     

     // 找到此驱动,但调用者不能加载此驱动,则抛出异常

     if ( getCallerClass(callerCL, di.driverClassName ) != di.driverClass ) {

         throw new SecurityException();

     }     

     // 在以上所有操作后,可以删除此驱动了

     drivers.removeElementAt(i);     

    }

 

    // 得到当前所有加载的 JDBC 驱动的枚举 **

    public static synchronized java.util.Enumeration getDrivers() {

        java.util.Vector result = new java.util.Vector();

        if (!initialized) {     initialize();      }              // 该类没有初始化时,必须完成初始化工作

                                                                                                           // 详情请阅读初始化部分

                ClassLoader callerCL = DriverManager.getCallerClassLoader();    // 得到当前类的类加载器  

        for (int i = 0; i < drivers.size(); i++) {                                                           // 遍列所有的驱动

            DriverInfo di = (DriverInfo)drivers.elementAt(i);                                 // 得到某个具体的驱动

                     // 假如调用者没有许可加载此驱动时,忽略该驱动

             if ( getCallerClass(callerCL, di.driverClassName ) != di.driverClass ) {

                println("    skipping: " + di);

                continue;

            }

            result.addElement(di.driver);                        // 将可以加载的驱动加入要返回的结果集

        }

        return (result.elements());                             // 返回结果集

    }

 

           private static native ClassLoader getCallerClassLoader();

              // 获得当前调用者的类装载器的本地方法(关于本地方法 JNI 请关注本博客后续文章)

 

// 返回类对象。我们使用 DriverManager 的本地方法 getCallerClassLoader() 得到调用者的类加载器

  private static Class getCallerClass(ClassLoader callerClassLoader, String driverClassName) {

                       // callerClassLoader 为类加载器, driverClassName 为驱动的名称

     Class callerC = null;

     try {

         callerC = Class.forName(driverClassName, true, callerClassLoader);

              // 使用指定的类装载器定位、加载指定的驱动类,

              //true 代表该驱动类在没有被初始化时会被初始化,返回此类

     }catch (Exception ex) {

         callerC = null;           // being very careful

     }

     return callerC;

  }

 

 

3.建立连接

      JDBC 程序中一般使用 DriverManager.getConnection 方法返回一个连接。该方法有多个变体,它们都使用了具体驱动类的 connect 方法实现连接。下面是连接的核心方法。

 

private static Connection getConnection(String url, java.util.Properties info, ClassLoader callerCL)

throws SQLException {        

     // 当类加载器为 null 时,必须检查应用程序的类加载器

         // 其它在 rt.jar 之外的 JDBC 驱动类可以从此加载驱动   /*

         synchronized(DriverManager.class) {                    // 同步当前 DriverManger 的类

           if(callerCL == null) {    callerCL = Thread.currentThread().getContextClassLoader();   }   

                   // 得到当前线程的类加载器(此句的真正含义请关注后续线程相关的文章)

         }        

         if(url == null) {    throw new SQLException("The url cannot be null", "08001");   }   

         println("DriverManager.getConnection( "" + url + " ")");   

         if (!initialized) {    initialize();         }                 // 必须初始化,将默认的驱动加入

         // 遍列当前的所有驱动,并试图建立连接

         SQLException reason = null;

         for (int i = 0; i < drivers.size(); i++) {

             DriverInfo di = (DriverInfo)drivers.elementAt(i);     

             // 假如调用者没有许可加载该类,忽略它

             if ( getCallerClass(callerCL, di.driverClassName ) != di.driverClass ) {                  

                            // 当驱动不是被当前调用者的类加载器加载时忽略此驱动

                   println("    skipping: " + di);

                   continue;

             }

             try {

                   println("    trying " + di);

                   Connection result = di.driver.connect(url, info);           // 调用某个驱动的连接方法建立连接

                   if (result != null) {                                                // 在建立连接后打印连接信息且返回连接

                       println("getConnection returning " + di);

                       return (result);

                   }

             } catch (SQLException ex) {                            

                   if (reason == null) {    reason = ex;              }                 // 第一个错误哦

             }

         }   

         // 以上过程要么返回连接,要么抛出异常,当抛出异常会给出异常原因,即给 reason 赋值

         // 在所有驱动都不能建立连接后,若有错误则打印错误且抛出该异常

         if (reason != null)    {

             println("getConnection failed: " + reason);

             throw reason;

         }   

         // 若根本没有返回连接也没有异常,否则打印没有适当连接,且抛出异常

         println("getConnection: no suitable driver");

         throw new SQLException("No suitable driver", "08001");

    }

 

// 以下三个方法是上面的连接方法的变体,都调用了上面的连接方法

    public static Connection getConnection(String url, java.util.Properties info) throws SQLException {

                  ClassLoader callerCL = DriverManager.getCallerClassLoader();                 

                            // 没有类加载器时就是该调用者的类加载器

        return (getConnection(url, info, 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);     }

        return (getConnection(url, info, callerCL));

    }

 

    public static Connection getConnection(String url) throws SQLException {

        java.util.Properties info = new java.util.Properties();

                  ClassLoader callerCL = DriverManager.getCallerClassLoader();

        return (getConnection(url, info, callerCL));

    }

 

4.几个开源数据库的驱动类

 

以下第一个是 smallsql 中驱动类 SSDriver 的源代码:

package smallsql.database;

import java.sql.*;

import java.util.Properties;

public class SSDriver implements Driver {

         static SSDriver drv;

    static {

        try{

                 drv = new SSDriver();

            java.sql.DriverManager.registerDriver(drv);

        }catch(Throwable e){}

         }

 

    public Connection connect(String url, Properties info) throws SQLException {

        if(!acceptsURL(url)) return null;

                   ……

        return new SSConnection( (idx > 0) ? url.substring(idx+1) : null);

    }

         ……

}

从上面红色的部分可以看到:这是一个静态语句块( static block ),这意味着该语句是在类构造完成前完成的(关于语句块的加载请阅读《 Think in java 》)。即调用 class.forName(“smallsql.database.SSDriver”) 语句时,会首先创建一个 SSDriver 的实例,并且将其向驱动管理器 (DriverManager) 注册。这样就完成驱动的注册了。

从上面的蓝色的代码可以看出:驱动的连接方法返回的是一个具体的 SSConnection 对象。而在前面研究的 Driver 接口中返回的是 Connection 接口,这是不茅盾的, SSConnection 对象实现了 Connection 接口。其实其他数据库驱动也是一样,首先用一个static在类加载时注册驱动,然后实现Driver的connect方法,方法中实际返回一个实现Connection的子类对象,这个子类也是由我们重新实现的。

 

5.驱动加载顺序

 

       以上是 JDBC 中驱动加载的时序图。时序图主要有以下 7 个动作:

1.         客户调用 Class.forName(“XXXDriver”) 加载驱动。

2.         此时此驱动类首先在其静态语句块中初始化此驱动的实例,

3.         再向驱动管理器注册此驱动。

4.         客户向驱动管理器 DriverManager 调用 getConnection 方法,

5.         DriverManager 调用注册到它上面的能够理解此 URL 的驱动建立一个连接,

6.         在该驱动中建立一个连接,一般会创建一个对应于数据库提供商的 XXXConnection 连接对象,

7.         驱动向客户返回此连接对象,不过在客户调用的 getConnection 方法中返回的为一个 java.sql.Connection 接口,而具体的驱动返回一个实现 java.sql.Connection 接口的具体类。

       以上就是驱动加载的全过程。由此过程我们可以看出 JDBC 的其它一些特点

 

 

6.JDBC调用架构

 

 

红色的是抽象,蓝色的抽象方法获得实际的实现类,这样就只需要知道调用顺序,任何jdbc都可以用了,这种抽象和具体的连接是由 java RTTI 支持的,不懂可以阅读《 Think in java 》。在接下来的结果集的处理 rs 也是抽象的吧!

       因此,在写 JDBC 程序时,即使我们使用不同数据库提供商的数据库我们只要改变驱动类的地址,和具体连接的 URL 及其用户名和密码,其它几乎不用任何修改,就可以完成同样的工作!

 

就总结这么多拉 ,下1篇我会把自己学习总结出来的JDBC类加载底层的原理写出来,就是CallerClassLoader那块。

 

 

分享到:
评论

相关推荐

    JDBC驱动加载分析

    ### JDBC驱动加载分析 #### 背景与概念 在Java开发中,JDBC (Java Database Connectivity) 是一种用于执行SQL语句的标准Java API,它由一组用Java语言编写的类和接口组成。JDBC提供了Java应用程序与各种类型的关系...

    Java jdbc编程驱动(全)

    本资源“Java jdbc编程驱动(全)”包含了多种主流数据库系统的JDBC驱动,包括MySQL、Oracle 10g以及SQL Server,这使得Java开发者能够在不同的数据库环境下方便地进行开发。 1. **MySQL JDBC驱动**:MySQL的JDBC...

    JDBC驱动及JAR包

    JDBC(Java Database Connectivity)是Java编程语言中用于与各种数据库进行交互的一种标准接口。它由Sun Microsystems...同时,管理好JDBC驱动的JAR包,确保它们与数据库系统的版本相匹配,也是开发过程中的重要环节。

    SQL Server JDBC驱动jar包

    SQL Server JDBC驱动jar包是Java应用程序与Microsoft SQL Server数据库进行交互的重要组件。JDBC(Java Database Connectivity)是Java平台的标准接口,它允许Java程序通过Java API来连接和操作各种数据库,包括SQL ...

    Mysql 5.1.7 JDBC驱动包

    MySQL 5.1.7 JDBC驱动包是连接Java应用程序与MySQL数据库的重要组件。JDBC(Java Database Connectivity)是Java编程语言中的一个标准API,它允许Java程序员使用SQL语句来访问和操作数据库。在这个特定的版本中,...

    JDBC数据库驱动

    MySQL的JDBC驱动提供了Type 4驱动,它是一个全功能的、高性能的驱动,使得Java应用可以连接到MySQL数据库,支持最新的JDBC 8规范,包括SSL加密、存储过程调用等特性。 4. **SQL Server JDBC驱动**: - `sqljdbc...

    jdbc_驱动

    1. **加载驱动**:通过`Class.forName()`方法加载指定的JDBC驱动类,例如`com.microsoft.sqlserver.jdbc.SQLServerDriver`。 2. **建立连接**:使用`DriverManager.getConnection()`方法,传入数据库URL、用户名和...

    JDBC驱动插件

    使用这些JDBC驱动插件时,开发者需要按照JDBC规范在程序中加载对应的驱动,通常是通过`Class.forName()`方法指定驱动类的全限定名,然后使用`DriverManager.getConnection()`方法创建数据库连接。之后,可以创建...

    SQL Server2000的JDBC驱动

    SQL Server 2000的JDBC驱动属于类型4,即全Java实现,它提供了一个高效、高性能的直接连接方式,不需要依赖于ODBC。 安装SQL Server 2000的JDBC驱动通常包括以下几个步骤: 1. 下载:可以从微软官方网站获取SQL ...

    全套数据库驱动JDBC_driver

    - 加载驱动:使用`Class.forName()`方法加载对应的JDBC驱动类。 - 建立连接:通过`DriverManager.getConnection()`方法,输入数据库URL、用户名和密码建立连接。 - 创建Statement或PreparedStatement对象:用于...

    java jdbc sql server2000 驱动包

    总的来说,Java JDBC SQL Server 2000驱动包为Java开发者提供了与SQL Server 2000数据库进行高效、可靠的交互能力,涵盖了从建立连接、执行SQL到处理结果的全过程。开发者需要根据项目需求选择合适的驱动(msbase....

    sqlserver2000 jdbc驱动

    SQL Server 2000 JDBC驱动是用于Java应用程序与Microsoft SQL Server 2000数据库进行交互的一种关键组件。在Java编程环境中,JDBC(Java Database Connectivity)接口提供了标准的方法来连接、查询和操作数据库。SQL...

    x-pack-sql-jdbc.rar

    开发者可以利用X-Pack SQL JDBC驱动在Java Web应用、数据分析工具(如Tableau、Excel)或者ETL(提取、转换、加载)流程中,无缝对接Elasticsearch,简化数据处理过程。 总结,X-Pack SQL JDBC驱动为Elasticsearch...

    jdbc资源全套配置

    【jdbc资源全套配置】 ...总结来说,JDBC资源全套配置涵盖了从加载驱动、连接数据库到执行SQL、处理结果、事务管理以及使用连接池等全过程。通过学习和实践这些内容,开发者能够高效地进行数据库操作,提升应用性能。

    sqljdbc41.jar

    在了解sqljdbc41.jar之前,我们先科普一下JDBC驱动的四种类型: 1. 类型1:纯Java的Net Driver(JDBC-ODBC桥),依赖于本地ODBC驱动。 2. 类型2:部分Java,部分本地代码的驱动,通常用于访问数据库的API不是完全...

    jdbc连接数据库类

    - `dbDriver`:存储了 JDBC 驱动的全限定名,这里指定的是 Microsoft SQL Server 的 JDBC 驱动。 - `url`:数据库的连接 URL,包括服务器地址、端口号以及数据库名称等信息。 - `connection`:用于保存与数据库...

    JDBC教程

    JDBC驱动有四种类型,分别是: - **类型1(JDBC-ODBC桥)**: 通过ODBC驱动与数据库交互,不推荐使用,因为需要额外安装ODBC驱动。 - **类型2(部分Java驱动)**: 使用数据库厂商提供的本地API,速度较快,但不是纯...

    JDBC 连接过程(oracle)

    本文详细介绍了通过JDBC连接Oracle数据库的全过程,包括项目准备、添加JDBC驱动、加载驱动、建立连接、处理结果集以及异常处理等内容。通过上述步骤,开发者可以有效地实现Java应用程序与Oracle数据库之间的交互。

    jdbc参考手册

    JDBC驱动程序分为四大类型:JDBC-ODBC桥驱动程序、本地API部分Java驱动程序、JDBC网络桥驱动程序以及本地协议全Java驱动程序。每种类型的驱动程序有其特定的适用场景和性能特点。 使用JDBC的基本步骤通常包括以下几...

Global site tag (gtag.js) - Google Analytics