【转载】JDBC之驱动加载原理全面解析--源于为什么每家数据库都要提供自己驱动的疑问。

举报
灵犀晨 发表于 2022/03/11 11:45:50 2022/03/11
【摘要】 原文链接:https://blog.csdn.net/zhouym_/article/details/90580197JDBC概念Java DataBase Connectivity(Java数据库连接技术)它是将Java与SQL结合且独立于特定的数据库系统的应用程序编程接口(API-它是一种可用于执行SQL语句的JavaAPI,即由一组用Java语言编写的类与接口所组成)JDBC设计的目的...

原文链接:https://blog.csdn.net/zhouym_/article/details/90580197

JDBC概念
Java DataBase Connectivity(Java数据库连接技术)
它是将Java与SQL结合且独立于特定的数据库系统的应用程序编程接口(API-它是一种可用于执行SQL语句的JavaAPI,即由一组用Java语言编写的类与接口所组成)

JDBC设计的目的
它是一种规范,设计出来的主要目的是为了让各个数据库开发商为Java程序员提供标准的数据访问类和接口,使得独立于DBMS的Java应用程序的开发成为可能(数据库改变,驱动程序跟着改变,但是应用程序不变)
Java设计规范接口,各大数据库产商遵守规范实现,Java程序员不需要考虑实现细节,只需要调用API即可

在这里插入图片描述
JDBC组成
JDBC API (统一的应用接口)
JDBC Driver Manager(驱动程序管理器)
JDBC 数据库驱动程序 驱动本质就是一个Java类,这个类实现了JavaAPI定义的接口
一般情况下,在应用程序中进行数据库连接,调用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");
	//静态代码块,加载时优先加载静态代码块中的代码
	static {
		Timestamp localTimestamp = Timestamp.valueOf("2000-01-01 00:00:00.0");
		try {
			if (defaultDriver == null) {
				//创建一个OracleDriver实例,然后注册到DriverManager中
                Driver defaultDriver = new OracleDriver();
				DriverManager.registerDriver(defaultDriver);
			}
 
		} catch (RuntimeException localRuntimeException) {
			//省略异常代码
		} catch (SQLException localSQLException) {
			//省略异常代码
	}

Driver接口
public interface Driver
每个驱动程序类必须实现的接口。
Java SQL 框架允许多个数据库驱动程序。
每个驱动程序都应该提供一个实现 Driver 接口的类。

DriverManager 会试着加载尽可能多的它可以找到的驱动程序,然后,对于任何给定连接请求,它会让每个驱动程序依次试着连接到目标 URL。

强烈建议每个 Driver 类应该是小型的并且是单独的,这样就可以在不必引入大量支持代码的情况下加载和查询 Driver 类。

在加载某一 Driver 类时,它应该创建自己的实例并向 DriverManager 注册该实例。这意味着用户可以通过调用以下程序加载和注册一个驱动程序
Class.forName("foo.bah.Driver")

在这里插入图片描述

acceptsURL(String url) 方法用来测试对指定的url,该驱动能否打开这个url连接。driver对自己能够连接的url会制定自己的协议,只有符合自己的协议形式的url才认为自己能够打开这个url,如果能够打开,返回true,反之,返回false;
注意:这里acceptsURL(url)只会校验url是否符合协议,不会尝试连接判断url是否有效

例如:oracle定义的自己的url协议如下:

jdbc:oracle:thin:@//<host>:<port>/ServiceName
jdbc:oracle:thin:@<host>:<port>:<SID>

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");
						
			//3.创建真实的数据库连接:
			String  url = "jdbc:oracle:thin:@127.0.0.1:1521:orcl";
			Properties props = new Properties();
			props.put("user", "ls");
			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 内部持有这些注册进来的驱动 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:@::”);
实际上,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:orcl";
			
			//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{
			
		}
	}

使用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);

Class.forName("oracle.jdbc.driver.OracleDriver");
Connection connection = DriverManager.getConnection(url);

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);
			}
	}

	String url = "jdbc:oracle:thin:@127.0.0.1:1521:xe";
	//设置值系统变量jdbc.drivers
	System.setProperty("jdbc.drivers", "oracle.jdbc.driver.OracleDriver");
	//通过特定的url获取driver
	Driver driver = DriverManager.getDriver(url);
	//打印是否存在
	System.out.println(driver);

要注意的是:一定要在第一次使用DriverManager之前设置jdbc.drivers,因为DriverManager中的static静态代码段只会被执行一次!
————————————————
版权声明:本文为CSDN博主「zhouym_」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zhouym_/article/details/90580197

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。