跳到主要内容

数据库驱动是如何加载的

一、为什么需要JDBC?

在JDBC没有出现的时候,数据库是运行在电脑上的一个独立程序。所以如果某一个应用程序想要访问数据库需要通过网络通信协议与数据库进行命令交换。

20240222000807

如应用程序发出了一个增删改查的命令,那么这个命令就需要通过一次网络连接将该命令传送给数据库,让数据库进行相应的操作,并返回相应的结果。

为了简便开发,将程序员从繁琐的与数据库命令交互底层的操作解放出来,所以数据库提供商一般都会将其封装成API对外暴露功能。

但这样的命令交换自然会遇到一个困境,即数据库有很多种,Oracle,MySQL等,不同的数据库通常会有不同的通信协议,那么当一个应用程序涉及到几种库的时候,就不得不按照不同厂商的API写出几套连接程序来;而API的不同又导致了另一个问题,即每个API的实现方式不同,因为涉及到网络连接,API可能会依赖与操作系统的相关功能,那么需要更换操作系统时,可能需要更换支持对应操作系统的API;这个是绝对无法容忍的!问题的关键在于: 程序员无法通过一致的API去操作数据库,而JDBC的出现解决了这个问题,它提供了一致的编码规范,无论是哪一种数据库,在应用程序层面,都是通过一致的API去访问。

20240222000943

所以当我们的项目中要使用MySQL的时候,总是先导入MySQL驱动才能访问数据库,然后通过JDBC接口来访问,而JDBC接口则通过驱动来实现真正对数据库的访问。

二、导入MySQL依赖

应用在使用MySQL时,首先我们都会导入MySQL依赖,就是所谓的驱动。之后,我们就开始用代码加载数据库驱动程序连接数据库,进行增删改查。

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
<scope>runtime</scope>
</dependency>

在maven中查看Jar包如下图:

20240222001001

三、如何使用JDBC?

下面这段代码通常是我们连接数据库进行增删改查的第一步。

@Test
public void test03() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mysql_test", "root", "123456");
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("select * from user");
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
System.out.println("id=" + id + " name=" + name);
}
resultSet.close();
statement.close();
connection.close();
}

四、源代码解读

这一行代码使应用程序与数据库建立了链接。

Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mysql_test", "root", "123456");

4.1 DriverManager

4.1.1 介绍

DriverManager是Java JDBC(Java Database Connectivity) API中的一个类,它提供了管理和访问数据库驱动程序的功能。

JDBC是Java平台的标准数据库访问API,它定义了一组用于连接和操作数据库的接口和类。DriverManager类是JDBC的核心类之一,它主要用于以下几个方面:

  1. 加载数据库驱动程序:在使用JDBC连接数据库之前,需要通过DriverManager加载适当的数据库驱动程序。通过DriverManager.registerDriver()方法或者使用静态代码块,可以将数据库驱动程序注册到DriverManager中。
  2. 建立数据库连接:DriverManager提供了getConnection()方法,用于建立与数据库的连接。在调用此方法时,需要提供数据库的URL、用户名和密码等连接参数。
  3. 管理数据库连接:DriverManager可以管理多个数据库连接。通过getConnection()方法获取的Connection对象可以用于执行SQL语句和事务管理等操作。
  4. 卸载数据库驱动程序:在应用程序退出或不再需要使用某个数据库驱动程序时,可以使用DriverManager.deregisterDriver()方法将其从DriverManager中卸载。

综上所述,DriverManager类起到了连接数据库和管理数据库驱动程序的关键作用。它允许应用程序通过标准的JDBC API与各种数据库进行交互,并提供了一种灵活和可扩展的方式来连接和管理数据库。

20240222001319

4.1.2 源码解析

这个类构造方法是私有的,所以它的方法都是静态方法。当DriverManager进行初始化时,静态代码块中开始调用loadInitialDrivers()方法。

其中先对drivers进行赋值,通过获取系统属性jdbc.drivers进行赋值,最下面的代码通过Class.forName()会对此驱动进行初始化。这种方式已经不经常用了,在JDBC4.0及之后的版本中,可直接通过DriverManager的注册机制自动加载驱动程序,也就是loadInitialDrivers()这个方法。

在代码中我们可看到有一个ServiceLoader,这里不过多描述,后续文章单独讲解这块内容。这里先简单了解一下即可。

提示

ServiceLoader(Java SPI机制,全称:Service Provider Interfaces,服务提供接口) 是Java提供的一套供第三方实现或扩展使用的技术体系。主要通过解耦服务具体实现以及服务使用,使得程序的可扩展性大大增强,甚至可插拔。 基于服务的注册与发现机制,服务提供者向系统注册服务,服务使用者通过查找发现服务,可以达到服务的提供与使用的分离,甚至完成对服务的管理。 JDK中,基于SPI的思想,提供了默认具体的实现,ServiceLoader。利用JDK自带的ServiceLoader,可以轻松实现面向服务的注册与发现,完成服务提供与使用的解耦。 完成分离后的服务,使得服务提供方的修改或替换,不会给服务使用方带来代码上的修改,基于面向接口的服务约定,提供方和使用方各自直接面向接口编程,而不用关注对方的具体实现。同时,服务使用方使用到服务时,也才会真正意义上去发现服务,以完成服务的初始化,形成了服务的动态加载

(1)DriverManager##loadInitialDrivers()
static {
// 加载初始化驱动
loadInitialDrivers();
println("JDBC DriverManager initialized");
}

private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
// If the driver is packaged as a Service Provider, load it.
// Get all the drivers through the classloader
// exposed as a java.sql.Driver.class service.
// ServiceLoader.load() replaces the sun.misc.Providers()

AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {

ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();

/* Load these drivers, so that they can be instantiated.
* It may be the case that the driver class may not be there
* i.e. there may be a packaged driver with the service class
* as implementation of java.sql.Driver but the actual class
* may be missing. In that case a java.util.ServiceConfigurationError
* will be thrown at runtime by the VM trying to locate
* and load the service.
*
* Adding a try catch block to catch those runtime errors
* if driver not available in classpath but it's
* packaged as service and that service is there in classpath.
*/
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});

println("DriverManager.initialize: jdbc.drivers = " + drivers);

if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}

(2)ServiceLoader##load()

load中的方法参数传入的java.sql.Driver接口类,acc这个参数先不用看,先疏通主脉络。

  1. 获取当前线程的上下文类加载器
  2. ServiceLoader中service属性赋值为java.sql.Driver的Class对象
  3. ServiceLoader中loader属性,先判断cl是否为NULL,为NULL取系统类加载器,否则取当前线程的上下文加载器
  4. acc属性先跳过,不是此次重点。
  5. 调用reload()方法,将providers清空,并对lookupIterator属性赋值,创建LazyIterator惰性迭代器。
public static <S> ServiceLoader<S> load(Class<S> service) {
// 获取当前线程的上下文类加载器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}

private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}


public void reload() {
// providers清空
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
(3)ServiceLoader##iterator()

此方法创建了一个Iterator迭代器。

public Iterator<S> iterator() {
return new Iterator<S>() {

Iterator<Map.Entry<String,S>> knownProviders
= providers.entrySet().iterator();

public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}

public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}

public void remove() {
throw new UnsupportedOperationException();
}

};
}
(4)遍历返回的迭代器iterator
  1. while循环先调用了hasNext()方法,返回true之后调用next()方法。

  2. providers中如果没有值,则调用LazyIterator中的hasNext()方法。

  3. providers中如果没有元素,则调用LazyIterator中的next()方法。

try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}

Iterator<Map.Entry<String,S>> knownProviders
= providers.entrySet().iterator();

public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}

public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
(5)ServiceLoader##LazyIterator

这里衔接上面调用的hasNext()方法、next()方法。

假设acc为NULL,则调用hasNextService()方法,nextName为NULL,fullName是通过PREFIX+service.getName()拼接起来的。

PREFIX属于ServiceLoader中的一个静态常量,值为:META-INF/services/。拼接起来为META-INF/services/java.sql.Driver。我们可以找到这个文件。文件内容为:com.mysql.cj.jdbc.Driver。

20240222001045

configs = loader.getResources(fullName);这段代码加载文件资源;pending = parse(service, configs.nextElement())这段代码将文件的内容解析出来。之后对nextName赋值为com.mysql.cj.jdbc.Driver。

之后会调用next()方法,next()---> nextService(),之后看nextService()方法,里面使用Class.forName()方法将Driver加载到JVM中,但是并没有进行初始化,最后执行S p = service.cast(c.newInstance()),c.newInstance()对Driver进行实例化,并调用cast方法对象放入到providers中。

private class LazyIterator
implements Iterator<S>
{

Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;

private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}

private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}

private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}

public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}

public S next() {
if (acc == null) {
return nextService();
} else {
PrivilegedAction<S> action = new PrivilegedAction<S>() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}

public void remove() {
throw new UnsupportedOperationException();
}

}

20240222001103

在对Driver进行实例化时,我们看到这个类中有一个静态代码块,此时调用DriverManager的registerDriver方法将Driver注册到registeredDrivers链表中。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.mysql.cj.jdbc;

import java.sql.DriverManager;
import java.sql.SQLException;

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}

static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}





## DriverManager
public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {

registerDriver(driver, null);
}


public static synchronized void registerDriver(java.sql.Driver driver,
DriverAction da)
throws SQLException {

/* Register the driver if it has not already been added to our list */
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}

println("registerDriver: " + driver);

}

五、总结

JDK利用了SPI模式,对数据库驱动类进行加载初始化,引发其调用JDK中的DriverManager类的register方法进行注册。

六、参考文章

Java SPI机制:ServiceLoader