JDBC 驱动加载原理解析

Posted 思想累积

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JDBC 驱动加载原理解析相关的知识,希望对你有一定的参考价值。

1、JDBC概念

1.1 什么是 JDBC

JDBC 一般指 Java 数据库连接,(Java Database Connectivity,简称为 JDBC)。是 Java 语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了比如查询、更新数据库中数据的方法,我们常说的 JDBC 是面向关系型数据库的。

1.2 JDBC 基本结构图

一般情况下,在 application 中进行数据库连接,调用 JDBC 接口,首先需要将指定的 JDBC 驱动实现加载到 jvm 中,然后再进行使用,基本的结构图如下
在这里插入图片描述

2、驱动加载

2.1 加载

驱动是一个 class,将驱动进行加载到 jvm 中与加载普通类一样,使用 Class.forName("driverName") 进行加载

//加载mysql 数据库驱动  
Class.forName("com.mysql.jdbc.Driver");

//加载Oracle数据库驱动  
Class.forName("oracle.jdbc.driver.OracleDriver"); 

2.2 Driver 接口

驱动,首先需要实现 java.sql.Driver 接口,连接不同数据库的驱动类不同,但每个驱动类都需要提供一个实现了 java.sql.Driver 接口的类。在程序中由驱动器管理类 java.sql.DriverManager 去调用这些 Driver 实现

Driver 接口源码

package java.sql;

import java.util.logging.Logger;

public interface Driver {

	// 试图创建一个给定 url 的数据库连接,创建 Connection 对象
    Connection connect(String url, java.util.Properties info) throws SQLException;

	// 驱动程序是否可以打开 url 连接,判断 url 是否符合协议,符合协议形式的 url 才可以
    boolean acceptsURL(String url) throws SQLException;

	// 获取驱动的属性信息
    DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info) throws SQLException;

	// 获取驱动主版本号
    int getMajorVersion();

	// 获取驱动的次版本号
    int getMinorVersion()#;

	// 驱动程序是否是一个真正的 JDBC Compliant
    boolean jdbcCompliant();

    public Logger getParentLogger() throws SQLFeatureNotSupportedException;
}

手动加载 Driver

// 加载 mysql 驱动类,并实例化
Driver driver = (Driver)Class.forName("com.mysql.jdbc.Driver);
// 判断 url 是否符合 mysql 的 url 形式
boolean flag = driver.acceptsURL("jdbc:mysql://localhost:3306/test");
// 创建数据库连接
String  url = "jdbc:mysql://localhost:3306/test";  
Properties props = new Properties();  
props.put("user", "root");  
props.put("password", "root");  
Connection connection = driver.connect(url, props);

2.3DriverManager 驱动器管理类

如果有多个驱动 Driver,JDBC 提供了 DriverManager 对多个驱动进行统一管理
DriverManager 可以注册和删除加载的驱动程序,根据给定的 url 获取符合 url 协议的 Driver 或者建立 Connection 连接

DriverManager 源码

/**
 * Load the initial JDBC drivers by checking the System property
  * jdbc.properties and then use the {@code ServiceLoader} mechanism
  */
static {
    loadInitialDrivers(); // 加载配置在 jdbc.drivers 系统变量中的驱动
    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;
    }
    
   ...... 省略一部分代码

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

使用 Class.forName(“driverName”) 加载驱动类到 jvm 的时候,同时会执行这个驱动类中的静态代码块,创建一个 Driver 实例,然后调用 DriverManager 进行注册 Driver。

DriverManager 在第一次被调用时,它会被加载到内存中,然后执行它内部的 static 静态代码块,执行其中的 loadIntialDrivers() 静态方法,加载配置在 jdbc.driver 中的 Driver,配置在 jdbc.drivers 中的驱动 driver 会先被加载

注册 Driver 到 DriverManager

在加载某一个 Driver 类时,应该创建自己的实例并像 DriverManager 中注册自己的实例。比如 com.mysql.jdbc.Driver 的源码,使用静态代码块进行实现

在我们使用 Class.forName("com.mysql.jdbc.Driver"); 方法获取它的 Class 对象,com.mysql.jdbc.Driver 就会被 jvm 加载,连接,初始化,初始化就会执行其中的静态代码块,执行下面的 java.sql.DriverManager.registerDriver(new Driver()); 进行注册

 static {
 	try {
    	java.sql.DriverManager.registerDriver(new Driver());
   	} catch (SQLException E) {
		throw new RuntimeException("Can't register driver!");
    }
}
注册实例源码

将要注册的驱动信息放到一个 DriverInfo 中,然后再放到一个 List 中,在后面连接时候会用到

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

    }

3、为什么加载驱动使用 Class.forName()

3.1 Class.forName

Class.forName() 的作用就是让 JVM 查找并加载指定的类,如果类中有 static 代码块,就会执行 static 代码块中的内容

通过下面的 Class.forName 源码可以看到,Class.forName(className); 方法调用的是 Class.forName(className, true, classLoader); 方法,第一个参数是类名,第二个参数是是否进行初始化,默认为 true 进行初始化,初始化就会执行 static 代码块中的内容,static 参数也会被初始化

@CallerSensitive
public static Class<?> forName(String className)
    throws ClassNotFoundException {
    Class<?> caller = Reflection.getCallerClass();
    return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

@CallerSensitive
public static Class<?> forName(String name, boolean initialize,
                               ClassLoader loader)
    throws ClassNotFoundException
{
    Class<?> caller = null;
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        // Reflective call to get caller class is only needed if a security manager
        // is present.  Avoid the overhead of making this call otherwise.
        caller = Reflection.getCallerClass();
        if (sun.misc.VM.isSystemDomainLoader(loader)) {
            ClassLoader ccl = ClassLoader.getClassLoader(caller);
            if (!sun.misc.VM.isSystemDomainLoader(ccl)) {
                sm.checkPermission(
                    SecurityConstants.GET_CLASSLOADER_PERMISSION);
            }
        }
    }
    return forName0(name, initialize, loader, caller);
}

/** Called after security check for system loader access checks have been made. */
private static native Class<?> forName0(String name, boolean initialize,
                                        ClassLoader loader,
                                        Class<?> caller)
    throws ClassNotFoundException;

Class.forName() 方法,除了会将类的 .class 文件加载到 jvm 中,默认还会对类进行初始化,执行类中的 static 代码块

JDBC 规范中,每个 Driver 类都必须向 DriverManager 中注册自己,都会执行下面的代码,静态代码块内容在初始化的时候就进行了一次执行,所以后面可以直接使用,通过 DriverManager.getConnection() 连接

static {
 	try {
 		// 注册 Driver 到 DriverManager
    	java.sql.DriverManager.registerDriver(new Driver());
   	} catch (SQLException E) {
		throw new RuntimeException("Can't register driver!");
    }
}

3.2 其它加载类方法

使用 ClassLoader.getSystemClassLoader().loadClass() ,通过下面的源码可以看到,调用了一个重载的 loadClass(className, false); 方法,第二个参数默认为 false 表示不进行连接,只是将类加载到 jvm 中,不会进行初始化,也不会执行 static 代码块中的内容

public Class<?> loadClass(String name) throws ClassNotFoundException {
	return loadClass(name, false);
}

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                c = findClass(name);

                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

以上是关于JDBC 驱动加载原理解析的主要内容,如果未能解决你的问题,请参考以下文章

JDBC驱动加载原理全面解析

JDBC原理

终结篇:MyBatis原理深入解析

jvm原理(24)通过JDBC驱动加载深刻理解线程上下文类加载器机制

JDBC的工作原理是啥?

关于mysql驱动版本报错解决,Cause: com.mysql.jdbc.exceptions.jdbc4Unknown system variable ‘query_cache_size(代码片段