Heim  >  Artikel  >  Datenbank  >  老调重弹:JDBC系列之<驱动加载原理全面解析)

老调重弹:JDBC系列之<驱动加载原理全面解析)

WBOY
WBOYOriginal
2016-06-07 15:57:16964Durchsuche

前言 最近在研究Mybatis框架,由于该框架基于JDBC,想要很好地理解和学习Mybatis,必须要对JDBC有较深入的了解。所以便把JDBC 这个东东翻出来,好好总结一番,作为自己的笔记,也是给读者一个参考~~~ 概述 一般情况下,在应用程序中进行数据库连接,调用J

前言

最近在研究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")。以下是将常用的数据库驱动加载到内存中的代码:

 

			//加载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()注册:

	static {
		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:@//:/ServiceName

jdbc:oracle:thin:@::

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定义了自己应该接收什么类型的URL,自己能打开什么类型的URL连接(注意:这里acceptsURL(url)只会校验url是否符合协议,不会尝试连接判断url是否有效) 。拓展阅读:常用数据库 JDBC URL格式

connect(String url,Properties info)方法,创建Connection对象,用来和数据库的数据操作和交互,而Connection则是真正数据库操作的开始(在此方法中,没有规定是否要进行acceptsURL()进行校验)。

手动加载驱动 Driver 并实例化进行数据库操作的例子

	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:@::或者jdbc:oracle:thin:@//:/ServiceName的参数给DriverManager.getDriver(String url)即可:

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

实际上,DriverManager.getDriver(String url)方法是根据传递过来的URL,遍历它维护的驱动Driver,依次调用驱动的Driver的acceptsURL(url),如果返回acceptsURL(url)返回true,则返回对应的Driver:

	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实例。以下代码将验证此说法:

	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对象实例(具体步骤请看注释):

	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对象实例。

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 的管理器,它在第一次被使用的过程中(即在代码中第一次用到的时候),它会被加载到内存中,然后执行其定义的static静态代码段,在静态代码段中,有一个 loadInitialDrivers() 静态方法,用于加载配置在jdbc.drivers 系统属性内的驱动Driver,配置在jdbc.drivers 中的驱动driver将会首先被加载:

	static {
		loadInitialDrivers();//加载配置在jdbc.drivers系统变量中的驱动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加载对应的driver
				Class.forName(str2, true, ClassLoader.getSystemClassLoader());
			} catch (Exception localException2) {
				println("DriverManager.Initialize: load failed: "
						+ localException2);
			}
	}

以下是通过jdbc.drivers加载驱动driver的方式:

			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系列 之

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn