为何使用JDBC操做MySQL须要添加Class.forName("com.mysql.jdbc.Driver")

引言

若是熟悉使用JDBC来链接数据库的同窗必定很清楚链接数据库的代码中必定会有依据Class.forName("com.mysql.jdbc.Driver");java

public static Connection getConnection() throws ClassNotFoundException, SQLException {
        if(connection == null){
            Class.forName("com.mysql.jdbc.Driver");
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/xxx?serverTimezone=UTC", "root", "xxxxxx");
        }
        return connection;
    }

以前没有想过为何须要有这么一个语句,都是按照文档直接这么作的,在这篇文章中我来试着解释这么作的缘由。mysql

类加载机制

在这以前咱们先来讲下Java中的类加载机制。sql

在Java中若是想要使用一个类,则必需要求该类已经被加载到Jvm中,加载的过程实际上就是经过类的全限定名来获取定义该类二进制字节流,而后将这个字节流所表示的静态存储结构转换为方法去的动态运行时数据结构。同时在在内存中实例化一个java.lang.Class对象,做为方法区中该类的数据访问入口(供咱们使用)。数据库

而会触发类加载的会有以下几种状况(引用自< <深刻理解java虚拟机> >): 数据结构

  1. 遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,若是类没有进行过初始化,则须要先触发其初始化。生成这4条指令的最多见的Java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
  2. 使用java.lang.reflect包的方法对类进行反射调用的时候,若是类没有进行过初始化,则须要先触发其初始化。
  3. 当初始化一个类的时候,若是发现其父类尚未进行过初始化,则须要先触发其父类的初始化。
  4. 当虚拟机启动时,用户须要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。

Class.forName

在Java官方文档中对Class.forName的解释为在运行时动态的加载一个类,返回值为生成的Class对象。函数

那么很明显在jdbc中使用Class.forName("com.mysql.jdbc.Driver");仅仅就是将com.mysql.jdbc.Driver类加载到Jvm中了,这个缘由大多数人应该都知道。测试

可是咱们要知道Class.forName貌似只是对类进行了加载,咱们甚至都没有对返回的Class对象作任何操做,那么咱们为何后面就能够直接用了呢?优化

首先看Class.forName调用了native方法forName0(...);code

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

private static native Class<?> forName0(String name, boolean initialize,ClassLoader loader,Class<?> caller);

要注意forName0中有一个关键的参数boolean initialize,;该参数用来标识在将该类加载后是否进行初始化操做。能够看到代码中是true,就表示会进行初始化操做。server

初始化过程实际上就是对变量赋值(不是赋初值,不会调用构造函数)的过程。包含全部类变量的赋值以及静态代码语句块的执行代码,包括对父类的初始化。

再看com.mysql.jdbc.Driver驱动类:

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,而静态代码块的内容会在初始化的过程当中执行,因此才能经过DriverManager.getConnection直接获取一个链接。

其余加载类方法

咱们须要明白的是在Java中并非只有经过Class.forName()才能显示的加载类。那么为何不使用其余的加载方法而恰恰选择Class.forName()呢?

ClassLoader.getSystemClassLoader().loadClass()

经过类加载器也能够将一个类加载到Jvm中。经过ClassLoader.getSystemClassLoader().loadClass("com.mysql.jdbc.Driver");也能够加载驱动类。

可是若是咱们深刻看下loadClass的实现:

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

protected Class<?> loadClass(String name, boolean resolve);

能够看到其调用了一个重载的方法,该方法也有一个boolean类型的变量boolean resolve,调用时默认为false。该参数用于标识是否对加载后的类进行连接操做,若是不进行链接操做则不会有初始化的操做。

因此若是使用这种加载类方式的话理论上来讲是没发使用该驱动类的。

new关键字

也可使用new关键字进行加载操做,在使用new关键字时会查看该类是否已经被加载,若是没有被加载的话则会进行加载操做。因此咱们的类中也能够这样写:

public static Connection getConnection() throws ClassNotFoundException, SQLException {
    if(connection == null){
        new Driver();//会自动调用静态代码块
        connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/xxx?serverTimezone=UTC", "root", "xxxx");
    }
    return connection;
}

可是实际上由于在驱动类的静态代码快中实际上已经有了实例化对象并注册到DriverMananger中的操做。因此这里根本就没有在实例化一个对象的过程。使用Class.forName便可,这也算是一个优化的过程吧。

能够不使用Class.forName("com.mysql.jdbc.Driver")

在测试的过程当中发现即便不显示的使用Class.forName("com.mysql.jdbc.Driver")也可以链接到数据库,一时间以为很奇怪。

深刻跟踪代码后发现实际上只要咱们引入了mysql的驱动包,那么在使用时会根据驱动包下提供的配置文件默认的建立一个类。

driver

因此实际上只要引入了该驱动包,那么使用jdbc是能够直接经过DriverManage来获取链接。

public static Connection getConnection() SQLException {
    return DriverManager.getConnection("jdbc:mysql://localhost:3306/xxx?serverTimezone=UTC", "root", "xxxxxx");
}
相关文章
相关标签/搜索