老调重弹:JDBC系列之<驱动加载原理全面解析) ----转
- 最近在研究Mybatis框架,由于该框架基于JDBC,想要很好地理解和学习Mybatis,必须要对JDBC有较深入的了解。所以便把JDBC 这个东东翻出来,好好总结一番,作为自己的笔记,也是给读者一个参考~~~
概述
一般情况下,在应用程序中进行数据库连接,调用JDBC接口,首先要将特定厂商的JDBC驱动实现加载到系统内存中,然后供系统使用。基本结构图如下:
驱动加载入内存的过程
这里所谓的驱动,其实就是实现了java.sql.Driver接口的类。如oracle的驱动类是 oracle.jdbc.driver.OracleDriver.class(此类可以在oracle提供的JDBC jar包中找到),此类实现了java.sql.Driver接口。
由于驱动本质上还是一个class,将驱动加载到内存和加载普通的class原理是一样的:使用Class.forName("driverName")。以下是将常用的数据库驱动加载到内存中的代码:
1
2
3
4
5
6
7
8
|
//加载Oracle数据库驱动 Class.forName( "oracle.jdbc.driver.OracleDriver" ); //加载SQL Server数据库驱动 Class.forName( "com.microsoft.sqlserver.jdbc.SQLServerDriver" ); //加载MySQL 数据库驱动 Class.forName( "com.mysql.jdbc.Driver" ); |
注意:Class.forName()将对应的驱动类加载到内存中,然后执行内存中的static静态代码段,代码段中,会创建一个驱动Driver的实例,放入DriverManager中,供DriverManager使用。
例如,在使用Class.forName() 加载oracle的驱动oracle.jdbc.driver.OracleDriver时,会执行OracleDriver中的静态代码段,创建一个OracleDriver实例,然后调用DriverManager.registerDriver()注册:
123456789101112static
{
Timestamp
localTimestamp =
Timestamp
.valueOf(
"2000-01-01 00:00:00.0"
);
try {
if (defaultDriver ==
null
) {
//创建一个OracleDriver实例,然后注册到DriverManager中
defaultDriver = new OracleDriver();
DriverManager.registerDriver(defaultDriver);
}
} catch (RuntimeException localRuntimeException) {
} catch (SQLException localSQLException) {
}
Driver的功能
java.sql.Driver接口规定了Driver应该具有以下功能:
其中:
acceptsURL(String url) 方法用来测试对指定的url,该驱动能否打开这个url连接。driver对自己能够连接的url会制定自己的协议,只有符合自己的协议形式的url才认为自己能够打开这个url,如果能够打开,返回true,反之,返回false;
例如:oracle定义的自己的url协议如下:
jdbc:oracle:thin:@//<host>:<port>/ServiceName
jdbc:oracle:thin:@<host>:<port>:<SID>
oracle自己的acceptsURL(String url)方法如下:
12345678910111213141516171819202122232425262728293031323334353637383940public
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定义了自己应该接收什么类型的URL,自己能打开什么类型的URL连接(注意:这里acceptsURL(url)只会校验url是否符合协议,不会尝试连接判断url是否有效) 。拓展阅读:常用数据库 JDBC URL格式
connect(String url,Properties info)方法,创建Connection对象,用来和数据库的数据操作和交互,而Connection则是真正数据库操作的开始(在此方法中,没有规定是否要进行acceptsURL()进行校验)。
手动加载驱动 Driver 并实例化进行数据库操作的例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
public static void driverTest(){ try { //1.加载oracle驱动类,并实例化 Driver driver = (Driver) Class.forName( "oracle.jdbc.driver.OracleDriver" ).newInstance(); //2.判定指定的URL oracle驱动能否接受(符合oracle协议规则) boolean flag = driver.acceptsURL( "jdbc:oracle:thin:@127.0.0.1:1521:xe" ); //标准协议测试 boolean standardFlag1 = driver.acceptsURL( "jdbc:oracle:thin:@//<host>:<port>/ServiceName" ); boolean standardFlag2 = driver.acceptsURL( "jdbc:oracle:thin:@<host>:<port>:<SID>" ); System. out .println( "协议测试:" +flag+ "\t" +standardFlag1+ "\t" +standardFlag2); //3.创建真实的数据库连接: 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 对象用于数据库交互,代码省略。。。。。 } catch (Exception e) { System. out .println( "加载Oracle类失败!" ); e.printStackTrace(); } finally{ } } |
上述的手动加载Driver并且获取连接的过程稍显笨拙:如果现在我们加载进来了多个驱动Driver,那么手动创建Driver实例,并根据URL进行创建连接就会显得代码杂乱无章,并且还容易出错,并且不方便管理。JDBC中提供了一个DriverManager角色,用来管理这些驱动Driver。
DriverManager角色
事实上,一般我们操作Driver,获取Connection对象都是交给DriverManager统一管理的。DriverManger可以注册和删除加载的驱动程序,可以根据给定的url获取符合url协议的驱动Driver或者是建立Conenction连接,进行数据库交互。
以下是DriverManager的关键方法摘要:
DriverManager 内部持有这些注册进来的驱动 Driver,由于这些驱动都是 java.sql.Driver 类型,那么怎样才能获得指定厂商的驱动Driver呢?答案就在于:
java.sql.Driver接口规定了厂商实现该接口,并且定义自己的URL协议。厂商们实现的Driver接口通过acceptsURL(String url)来判断此url是否符合自己的协议,如果符合自己的协议,则可以使用本驱动进行数据库连接操作,查询驱动程序是否认为它可以打开到给定 URL 的连接。
使用DriverManager获取指定Driver
对于驱动加载后,如何获取指定的驱动程序呢?这里,DriverManager的静态方法getDriver(String url)可以通过传递给的URL,返回可以打开此URL连接的Driver。 比如,我想获取oracle的数据库驱动,只需要传递形如jdbc:oracle:thin:@<host>:<port>:<SID>或者jdbc:oracle:thin:@//<host>:<port>/ServiceName的参数给DriverManager.getDriver(String url)即可:
1
|
Driver oracleDriver =DriverManager.getDriver( "jdbc:oracle:thin:@<host>:<port>:<SID>" ); |
实际上,DriverManager.getDriver(String url)方法是根据传递过来的URL,遍历它维护的驱动Driver,依次调用驱动的Driver的acceptsURL(url),如果返回acceptsURL(url)返回true,则返回对应的Driver:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public static Driver getDriver(String paramString) throws SQLException { //省略部分代码。。。。 Iterator localIterator = registeredDrivers.iterator(); //遍历注册的驱动 while (localIterator.hasNext()) { DriverInfo localDriverInfo = (DriverInfo) localIterator. next (); if (isDriverAllowed(localDriverInfo.driver, localClass)) try { //如果accepsURL() 为 true ,返回对应的driver if (localDriverInfo.driver.acceptsURL(paramString)) { //返回对应的driver return localDriverInfo.driver; } } catch (SQLException localSQLException) { } else println( " skipping: " + localDriverInfo.driver.getClass().getName()); } throw new SQLException( "No suitable driver" , "08001" ); // -----省略部分代码 } |
使用DriverManager注册和取消注册驱动Driver
在本博文开始的 驱动加载的过程 一节中,讨论了当使用Class.forName("driverName")加载驱动的时候,会向DriverManager中注册一个Driver实例。以下代码将验证此说法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public static void defaultDriver(){ try { String url = "jdbc:oracle:thin:@127.0.0.1:1521:xe" ; //1.将Driver加载到内存中,然后执行其 static 静态代码,创建一个OracleDriver实例注册到DriverManager中 Class.forName( "oracle.jdbc.driver.OracleDriver" ); //取出对应的oracle 驱动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加载到内存中;
2. 然后便调用DriverManager.getDriver()去取Driver实例;
3. 将driver实例从DriverManager中注销掉;
4.尝试再取 对应url的Driver实例;
上述代码执行的结果如下:
从执行结果看,正好能够验证以上论述:当第四步再次获取对应url的 Driver 实例时,由于已经被注销掉了,找不到适当的驱动Driver,抛出了 "Not suitable driver" 的异常。
将上述的例子稍作变化,在注销掉了静态块创建的driver后,往DriverManager注册一个自己创建的Driver对象实例(具体步骤请看注释):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
public static void defaultDriver(){ try { String url = "jdbc:oracle:thin:@127.0.0.1:1521:xe" ; //1.将Driver加载到内存中,然后执行其 static 静态代码,创建一个OracleDriver实例注册到DriverManager中 Driver dd = (Driver)Class.forName( "oracle.jdbc.driver.OracleDriver" ).newInstance(); //2.取出对应的oracle 驱动Driver Driver driver = DriverManager.getDriver(url); System. out .println( "加载类后,获取Driver对象:" +driver); //3. 将driver从DriverManager中注销掉 DriverManager.deregisterDriver(driver); //4.此时DriverManager中已经没有了驱动Driver实例,将创建的dd注册到DriverManager中 DriverManager.registerDriver(dd); //5.重新通过url从DriverManager中取Driver driver = DriverManager.getDriver(url); System. out .println( "注销掉静态创建的Driver后,重新注册的Driver: " +driver); System. out .println( "driver和dd是否是同一对象:" +(driver==dd)); } catch (Exception e) { System. out .println( "加载Oracle类失败!" ); e.printStackTrace(); } finally{ } } |
以下代码运行的结果:
以上代码先创建了一个Driver对象,在注销了DriverManager中由加载驱动过程中静态创建驱动之后,注册到系统中,现在DriverManager中对应url返回的Driver 即是在代码中创建的Driver对象。
使用DriverManager创建 Connection 连接对象
创建 Connection 连接对象,可以使用驱动Driver的 connect(url,props),也可以使用 DriverManager 提供的getConnection()方法,此方法通过url自动匹配对应的驱动Driver实例,然后调用对应的connect方法返回Connection对象实例。
1
2
|
Driver driver = DriverManager.getDriver(url); Connection connection = driver. connect (url, props); |
上述代码等价于:
1
2
|
Class.forName( "oracle.jdbc.driver.OracleDriver" ); Connection connection = DriverManager.getConnection(url, props); |
jdbc.drivers
DriverManager 作为 Driver 的管理器,它在第一次被使用的过程中(即在代码中第一次用到的时候),它会被加载到内存中,然后执行其定义的static静态代码段,在静态代码段中,有一个 loadInitialDrivers() 静态方法,用于加载配置在jdbc.drivers 系统属性内的驱动Driver,配置在jdbc.drivers 中的驱动driver将会首先被加载:
1
2
3
4
5
|
static { loadInitialDrivers();//加载配置在jdbc.drivers系统变量中的驱动driver println( "JDBC DriverManager initialized" ); SET_LOG_PERMISSION = new SQLPermission( "setLog" ); } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
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加载对应的driver Class.forName(str2, true , ClassLoader.getSystemClassLoader()); } catch (Exception localException2) { println( "DriverManager.Initialize: load failed: " + localException2); } } |
以下是通过jdbc.drivers加载驱动driver的方式:
1
2
3
4
5
6
7
|
String url = "jdbc:oracle:thin:@127.0.0.1:1521:xe" ; //设置值系统变量jdbc.drivers System.setProperty( "jdbc.drivers" , "oracle.jdbc.driver.OracleDriver" ); //2.通过特定的url获取driver Driver driver = DriverManager.getDriver(url); //打印是否存在 System. out .println(driver); |
请记住:一定要在第一次使用DriverManager之前设置jdbc.drivers,因为DriverManager中的static静态代码段只会被执行一次!
-------------------------------------------------------------------------------------------------------------------------------------------------------------
以上是本文 老调重弹:JDBC系列 之 <驱动加载原理全面解析> 的全部内容,以上是自己心得,并非权威,如有不妥或者此错误之处,欢迎读者批评和斧正! 欢迎关注我的下一篇博文: 老调重弹:JDBC系列 之 <JDBC层次结构和基本构成>
老调重弹:JDBC系列之<驱动加载原理全面解析) ----转的更多相关文章
- (DT系列四)驱动加载中, 如何取得device tree中的属性
本文以At91rm9200平台为例,从源码实现的角度来分析驱动加载时,Device tree的属性是如何取得的.一:系统级初始化DT_MACHINE_START 主要是定义"struct m ...
- 【转】(DT系列四)驱动加载中, 如何取得device tree中的属性
原文网址:http://www.cnblogs.com/biglucky/p/4057488.html 本文以At91rm9200平台为例,从源码实现的角度来分析驱动加载时,Device tree的属 ...
- 如何调整Linux内核启动中的驱动初始化顺序-驱动加载优先级
Linux内核为不同驱动的加载顺序对应不同的优先级,定义了一些宏: include\linux\init.h #define pure_initcall(fn) __define_initcall(& ...
- iOS 下拉刷新-上拉加载原理
前言 讲下拉刷新及上拉加载之前先给大家解释UIScrollView的几个属性 contentSize是UIScrollView可以滚动的区域. contentOfinset 苹果官方文档的解释是&qu ...
- RX系列四 | RxAndroid | 加载图片 | 提交表单
RX系列四 | RxAndroid | 加载图片 | 提交表单 说实话,学RxJava就是为了我们在Android中运用的更加顺手一点,也就是RxAndroid,我们还是先一步步来,学会怎么去用的比较 ...
- linux设备和驱动加载的先后顺序
点击打开链接 Linux驱动先注册总线,总线上可以先挂device,也可以先挂driver,那么究竟怎么控制先后的顺序呢. Linux系统使用两种方式去加载系统中的模块:动态和静态. 静态加载:将所有 ...
- composer 实现自动加载原理
简介 一般在框架中都会用到composer工具,用它来管理依赖.其中composer有类的自动加载机制,可以加载composer下载的库中的所有的类文件.那么composer的自动加载机制是怎么实现的 ...
- Vue(基础七)_webpack(webpack异步加载原理)
---恢复内容开始--- 一.前言 1.webpack异步加载原理’ 2.webpack.ensure原理 ...
- 深入解析 composer 的自动加载原理 (转)
深入解析 composer 的自动加载原理 转自:https://segmentfault.com/a/1190000014948542 前言 PHP 自5.3的版本之后,已经重焕新生,命名空间.性状 ...
随机推荐
- 百度地图api简单使用方法
百度地图API的使用方法 百度地图API 开始学习百度地图API最简单的方式是看一个简单的示例.以下代码创建了一个520x340大小的地图区域并以天安门作为地图的中心: 1. <html&g ...
- (译)详解javascript立即执行函数表达式(IIFE)
写在前面 这是一篇译文,原文:Immediately-Invoked Function Expression (IIFE) 原文是一篇很经典的讲解IIFE的文章,很适合收藏.本文虽然是译文,但是直译的 ...
- 1,SFDC 管理员篇 - 基本设置
1, 公司配置 Setup | Administrator| Company Profile *Company Inforamtion:公司基础信息,License信息,重要的设置包括本地时间,币种, ...
- python基础07 函数
作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 函数最重要的目的是方便我们重复使用相同的一段程序. 将一些操作隶属于一个函数,以后 ...
- 关于rand()与srand()函数
rand函数功能为获取一个伪随机数(伪随机数的概念下面会有介绍). 一.函数名: rand(); 二.声明: int rand(); 三.所在头文件: stdlib.h 四.功能: 返回一个伪随机数. ...
- python some install tips
/* wooyun的小伙伴出了神器. 但是都是打包配置的.我本机又搭建了wamp,不能混合了,那就自己动手丰衣足食咯. */ python 2.7 已经安装. pip https://pip.pypa ...
- VC++ excel 2 operations
LPDISPATCH lpDisp; //lpdispatch,接口指针 // 设置为FALSE时,加上app.Quit(); // 否则EXCEL.EXE进程会一直存在,并且每操作一次就会多开一 ...
- nginx rewirte
server { listen 8888; server_name jobPhp; root F:\ck\Porject\quanRelease\quanJob; try_files $uri $ur ...
- anomaly detection algorithm
anomaly detection algorithm 以上就是异常监测算法流程
- Python-实现对表插入百万条数据
新手小白写的,我自己都不知道对不对,先写下来记着,以后掌握更多Python知识后,再来看下写的对不对. 题目:造一百万条大学生的基本信息(学校.专业.姓名.学号) 分析思路:利用Python的MySQ ...