Mysql Connector/J 源码分析(普通Connection)

本文深入分析了MySQL Connector/J的源码,详细阐述了数据库连接的创建过程,包括Socket层面和数据库管理软件层面的握手。在构造连接时,从加载驱动到建立Socket连接,再到数据库层面的认证,揭示了连接建立的复杂性和安全性。通过源码解析,展示了ConnectionImpl和JDBC4Connection的内部工作原理,以及异常处理机制,为理解MySQL与Java应用的交互提供了宝贵见解。
摘要由CSDN通过智能技术生成

前言

《Mysql Connector/J 源码分析(综述)》提到普通的Connection是最基本的连接。本文试图揭开它的内幕,看看它是如何与Mysql数据库管理软件(下文简称Mysql)交互的。

本次分析的版本为5.1.46。若通过maven下载,可添加以下依赖:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.46</version>
</dependency>

一、准备工作

撇开所有框架,我们最原始的获取Mysql连接的方式如下:

    Connection conn = null;
    URL =“jdbc:mysql://ip:port/dbname”;
    try{
        // 注册 JDBC 驱动
        Class.forName("com.mysql.jdbc.Driver");

        // 打开链接
        conn = DriverManager.getConnection(DB_URL,USER,PASS);
    ....

这几行代码表面上看,只是加载了"com.mysql.jdbc.Driver"这个类,然后把url和用户名与密码通过DriverManager#getConnection方法就能够得到连接了。貌似连Class#forName的方法调用都可以省略。但如果我们真把这句删掉,程序就跑不下去了。

以前有个牛人说过“简单是复杂的最终状态”,简单的两行代码,背后藏着不简单处理,目的就是让我们用得轻松,我们做软件设计时不正是抱着这宗旨吗?

我们看看com.mysql.jdbc.Driver的代码:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

    
    public Driver() throws SQLException {
        // Required for Class.forName().newInstance()
    }
}

从结构上看,它继承了NonRegisteringDriver,实现了java.sql.Driver接口。代码简洁,只有看上去不复杂的静态块和什么事情都没做的构造函数。

这里有一个常识性在里面,就是加载一个类的时候,静态块就会被执行。所以当程序执行Class.forName("com.mysql.jdbc.Driver")命令的时候,com.mysql.jdbc.Driver的静态块会被执行,但它并不是第一段被执行的静态块,原因是Driver继承了NonRegisteringDriver。所以程序会先执行NonRegisteringDriver的静态块,然后再执行Driver的静态块代码。NonRegisteringDriver的静态块所做的事情并不是本文要探讨的范围,看官可自行阅读。

在Driver的静态块里,只是构建一个Driver,然后就注册到 DriverManager。然而,DriverManger自己也有静态块,当程序执行java.sql.DriverManager.registerDriver(new Driver());命令的时候,DriverManger的静态块会先被执行,然后再执行registerDriver命令。它的静态块代码如下:

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

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

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

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

它的静态块主要是调用了loadInitialDrivers静态方法。该方法主要做的事情就是加载所有的驱动类到JDK里。这些驱动类来源于两部分,一部分是jdk的环境变量jdbc.drivers,另一部分使用了java spi机制,加载实现了“java.sql.Driver”接口的驱动类。当前jar包/META-INF/services/java.sql.Driver文件内容是:

com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver

所以,这些类都将会被加载。根据程序的执行顺序,com.mysql.jdbc.Driver的静态块正在执行中,后续将补充执行FabricMySQLDriver的静态块。

现在,我们简单的归纳下Class.forName("com.mysql.jdbc.Driver")命令,引发的一连串操作:

  • NonRegisteringDriver的静态块被执行,创建了一条守护线程,定时清空丢弃的连接。
  • com.mysql.jdbc.Driver的静态块被执行,但没执行完毕
  • DriverManager的静态块被执行,加载所有的驱动类进入JDK,从而轮循地触发每个驱动的静态块的执行。
  • 继续执行com.mysql.jdbc.Driver的静态块,构造com.mysql.jdbc.Driver对象并注册到DriverManager里。
  • 执行com.mysql.fabric.jdbc.FabricMySQLDriver的静态块,构造com.mysql.fabric.jdbc.FabricMySQLDriver对象并注册到DriverManager里。

所以,设计者就是利用静态块在类被加载进JDK时自动执行的特性,暗地里做了一些事情,包括将驱动注册到DriverManager。注册驱动的时候,驱动会被保存在DriverManager的registeredDrivers静态属性里。


二、构造连接

2.1 连接的实现类

当执行conn = DriverManager.getConnection(DB_URL,USER,PASS);命令的时候,我们跟踪调用链,会来到DriverManager#getConnection( String , java.util.Properties , Class<?> )方法:

private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
       ....

        for(DriverInfo aDriver : registeredDrivers) {
            // If the caller does not have permission to load the driver then
            // skip it.
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    println("    trying " + aDriver.driver.getClass().getName());
                    Connection con = aDriver.driver.connect(url, info);
                    if (con != null) {
                        // Success!
                        println("getConnection returning " + aDriver.driver.getClass().getName());
                        return (con);
                    }
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }

            } else {
                println("    skipping: " + aDriver.getClass().getName());
            }

        }

        ....
    }

该方法轮循已注册到DriverManager的驱动,如果哪个驱动能根据url等信息创建连接,就返回此连接,不再继续轮循其他驱动。不过,一个驱动无法根据url等信息成功地创建连接的原因会很多,所以这种轮循的机制存在进一步优化的空间。在当前,控制着注册到DriverManager的驱动数量也是一种折衷的方法。

从上面的代码片断看出,接下来的创建连接的工作交给驱动去做。因为只有com.mysql.jdbc.Driver才能解析到我们的url,所以由它的实例对象建立连接。但它并没有重载connect方法,所以这操作由父类NonRegisteringDriver#connect方法进行。

public java.sql.Connection conn
  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值