JDBC源码解析(一):示例+Driver注册流程
你是否想好好阅读一份源码,但是一直没有开始?
如果上面说到的几个问题你有遇到过,那么你可以继续阅读下面的内容了,我会按照自己的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的程序需要四步:
- 注册jdbc的driver
- 获取一个连接
- 进行查询
- 获取结果
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()方法,在源码中有着四个:
- getConnection(String url,String user, String password)
- getConnection(String url,java.util.Properties info)
- getConnection(String url)
- 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) { ...... }