JDBC源码解析(一):示例+Driver注册流程

JerryXia 发表于 , 阅读 (40)

你是否想好好阅读一份源码,但是一直没有开始?

如果上面说到的几个问题你有遇到过,那么你可以继续阅读下面的内容了,我会按照自己的jdbc源码阅读轨迹,来对jdbc的实现做一个详细的说明,尝试帮助自己和大家更深入地理解我们经常使用的这个工具。

如果你对上面的问题嗤之以鼻,那么恭喜你,下面的内容对你来讲要不就是太过简单,要不就是没有意义,就不用浪费时间继续看下去。

这里是前言

这是自己在16年的时候阅读jdbc时候留下的笔记,现在搬出来。当时写的一些废话也就不再删了。

自己一直说要某个开源项目的源码,但是一直没有真正地好好开始,一是以为看源码其实不容易看懂,而是因为选择犹豫,最后也很难敲定看哪个。

这次正式开始看jdbc的源码有两个三个:一是因为《java编程思想》这本书快看完了,折腾一个多月的时间,里面除了多线程和图形编程这两块基本都看得差不多了;一个是因为《设计模式之禅》这本书看了一半左右,里面的设计模式自己大致都明白是什么意思,但是印象不深刻,需要有一个稳固的过程;另一个是因为jdbc相对来说比较熟悉,加上以后肯定会常和数据库打交道,因此就决定看了。

在分析源码的时候,我会先自己理清思路,然后再回头来总结,总结的过程尽量地变成一个探索的过程,以此来梳理自己看源码的方法论。

文章结构

  • 在这篇博客里面,我会先举一个最经典的jdbc的使用例子,帮助大家回顾一下它的基本用法
  • 然后会来详细分析这个例子涉及到的jdbc的整个流程。
  • 然后我会对jdbc使用过程中,最基本的Driver注册流程进行分析,通过源码的讲解帮助大家来理解它。

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) {                    ......                }