Jdbc源码详解(二):获取connection

0x00 前言

上一节分析了jdbc的Driver注册过程,这一节分析一下jdbc是如何获取connection的。

0x01 connection的建立过程

DriverManager.getConnection 做了什么

conn = DriverManager.getConnection("jdbc:mysql://192.168.108.145/test", "root", "root");

可以看出,getConnection方法返回的是一个Connection对象,在下面的for循环中,会遍历registeredDrivers中存放的所有的Driver,直到找到一个合适的Driver,然后进行连接。通过这个方法我已经可以知道jdbc就是这样来获取连接的,但是具体是怎么来获取这个connection,还需要详细地看后面的代码。

private static Connection getConnection(
        String url, java.util.Properties info, ClassLoader callerCL) 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);
                    }
                }
    ......
    }

connect方法

java.sql.Driver接口中定义了一个connect方法,然后由具体的实现类来完成connect的功能。

我们先看一下mysql的实现。

com.mysql.jdbc.Driver中,并没有详细的实现,但是它继承了NonRegisteringDriver,那么connect应该就在NonRegisteringDriver中了。

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

现在定位到com.mysql.jdbc.NonRegisteringDriverconnect中。

可以看出,有一句if ((props = parseURL(url, info)) == null)来对url进行判断,如果url不符合规则的话,就会返回一个null值,这里的url就是指我们在程序中写的jdbc:mysql://192.168.108.145/test这样的语句。mysql的parseURL方法十分十分的长,暂时不再细究它了…

继续向下看,可以发现具体的获取连接的地方在com.mysql.jdbc.ConnectionImpl.getInstance

public java.sql.Connection connect(String url, Properties info) throws SQLException {
        if (url != null) {
            if (StringUtils.startsWithIgnoreCase(url, LOADBALANCE_URL_PREFIX)) {
                return connectLoadBalanced(url, info);
            } else if (StringUtils.startsWithIgnoreCase(url, REPLICATION_URL_PREFIX)) {
                return connectReplicationConnection(url, info);
            }
        }
        Properties props = null;
        if ((props = parseURL(url, info)) == null) {
            return null;
        }

        if (!"1".equals(props.getProperty(NUM_HOSTS_PROPERTY_KEY))) {
            return connectFailover(url, info);
        }

        try {
            Connection newConn = com.mysql.jdbc.ConnectionImpl.getInstance(host(props), port(props), props, database(props), url);
            return newConn;
        }
    ......
    }

漫长的寻找连接的道路

为了找到具体获取连接的地方,我们继续详细地向下找代码。

根据方法的调用关系,我们依次找到下面的四个方法,但是在最后一个方法这里线索断了一下。

  • com.mysql.jdbc.NonRegisteringDriver.connect
  • com.mysql.jdbc.ConnectionImpl.getInstance
  • com.mysql.jdbc.Util.handleNewInstance.handleNewInstance
  • java.lang.reflect.Constructor.newInstance

``

com.mysql.jdbc.ConnectionImpl

protected static Connection getInstance(String hostToConnectTo, int portToConnectTo, Properties info, String databaseToConnectTo, String url)
        throws SQLException {
    if (!Util.isJdbc4()) {
        return new ConnectionImpl(hostToConnectTo, portToConnectTo, info, databaseToConnectTo, url);
    }

    return (Connection) Util.handleNewInstance(JDBC_4_CONNECTION_CTOR, new Object[] { hostToConnectTo, Integer.valueOf(portToConnectTo), info,
            databaseToConnectTo, url }, null);
}

com.mysql.jdbc.Util.handleNewInstance

public static final Object handleNewInstance(Constructor<?> ctor, Object[] args, ExceptionInterceptor exceptionInterceptor) throws SQLException {
        try {

            return ctor.newInstance(args);
        } catch (IllegalArgumentException e) {
        ......
    }

java.lang.reflect.Constructor

public T newInstance(Object ... initargs)
    throws InstantiationException, IllegalAccessException,
           IllegalArgumentException, InvocationTargetException
{
    if (!override) {
        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
            Class<?> caller = Reflection.getCallerClass(2);

            checkAccess(caller, clazz, null, modifiers);
        }
    }
    if ((clazz.getModifiers() & Modifier.ENUM) != 0)
        throw new IllegalArgumentException("Cannot reflectively create enum objects");
    ConstructorAccessor ca = constructorAccessor;   // read volatile
    if (ca == null) {
        ca = acquireConstructorAccessor();
    }
    return (T) ca.newInstance(initargs);
}

也就是说自己找的方向有了偏差,在com.mysql.jdbc.ConnectionImpl.getInstance中,有一句return (Connection) Util.handleNewInstance(JDBC_4_CONNECTION_CTOR, ......),看来突破口在这里了。然后找JDBC_4_CONNECTION_CTOR是什么。

可以看出在这里,调用了Class.forName("com.mysql.jdbc.JDBC4Connection").getConstructor(......)。然后需要看一下com.mysql.jdbc.JDBC4Connection是做什么的。

com.mysql.jdbc.ConnectionImpl

static {
    mapTransIsolationNameToValue = new HashMap<String, Integer>(8);
    mapTransIsolationNameToValue.put("READ-UNCOMMITED", TRANSACTION_READ_UNCOMMITTED);
    mapTransIsolationNameToValue.put("READ-UNCOMMITTED", TRANSACTION_READ_UNCOMMITTED);
    mapTransIsolationNameToValue.put("READ-COMMITTED", TRANSACTION_READ_COMMITTED);
    mapTransIsolationNameToValue.put("REPEATABLE-READ", TRANSACTION_REPEATABLE_READ);
    mapTransIsolationNameToValue.put("SERIALIZABLE", TRANSACTION_SERIALIZABLE);

    if (Util.isJdbc4()) {
        try {
            JDBC_4_CONNECTION_CTOR = Class.forName("com.mysql.jdbc.JDBC4Connection").getConstructor(
                    new Class[] { String.class, Integer.TYPE, Properties.class, String.class, String.class });
        } catch (SecurityException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    } else {
        JDBC_4_CONNECTION_CTOR = null;
    }
}

继续找到了com.mysql.jdbc.JDBC4Connection,该类继承了com.mysql.jdbc.ConnectionImpl,因此在构造的时候也会先调用父类的构造器。

public class JDBC4Connection extends ConnectionImpl {
    private JDBC4ClientInfoProvider infoProvider;

    public JDBC4Connection(String hostToConnectTo, int portToConnectTo, Properties info, String databaseToConnectTo, String url) throws SQLException {
        super(hostToConnectTo, portToConnectTo, info, databaseToConnectTo, url);
    }
}

com.mysql.jdbc.ConnectionImpl构造器比较长,在其结尾处,发现它调用了NonRegisteringDriver.trackConnection(this)

public ConnectionImpl(String hostToConnectTo, int portToConnectTo, Properties info, String databaseToConnectTo, String url) throws SQLException {
    ......
    NonRegisteringDriver.trackConnection(this);
}

com.mysql.jdbc.NonRegisteringDriver

protected static void trackConnection(Connection newConn) {
    ConnectionPhantomReference phantomRef = new ConnectionPhantomReference((ConnectionImpl) newConn, refQueue);
    connectionPhantomRefs.put(phantomRef, phantomRef);
}

com.mysql.jdbc.NonRegisteringDriver.ConnectionPhantomReferenceNonRegisteringDriver的内部类。

static class ConnectionPhantomReference extends PhantomReference<ConnectionImpl> {
       private NetworkResources io;

       ConnectionPhantomReference(ConnectionImpl connectionImpl, ReferenceQueue<ConnectionImpl> q) {
           super(connectionImpl, q);

           try {
               this.io = connectionImpl.getIO().getNetworkResources();
           } catch (SQLException e) {
               // if we somehow got here and there's really no i/o, we deal with it later
           }
       }

       void cleanup() {
           if (this.io != null) {
               try {
                   this.io.forceClose();
               } finally {
                   this.io = null;
               }
           }
       }
   }

继续向下追,会用到两个比较重要的类:NetworkResourcesMysqlIO,他们和mysql的具体连接相关。

com.mysql.jdbc.NetworkResources

class NetworkResources {
    private final Socket mysqlConnection;
    private final InputStream mysqlInput;
    private final OutputStream mysqlOutput;
    protected NetworkResources(Socket mysqlConnection, InputStream mysqlInput, OutputStream mysqlOutput) {
        this.mysqlConnection = mysqlConnection;
        this.mysqlInput = mysqlInput;
        this.mysqlOutput = mysqlOutput;
    }
}

最后可以看出真正和mysql进行连接是在com.mysql.jdbc.MysqlIO中实现的,在这里有mysql连接的各种参数。

This class is used by Connection for communicating with the MySQL server.

//Mysql连接的一些设置参数
private static final int CLIENT_LONG_PASSWORD = 0x00000001; /* new more secure passwords */
private static final int CLIENT_FOUND_ROWS = 0x00000002;
private static final int CLIENT_LONG_FLAG = 0x00000004; /* Get all column flags */
protected static final int CLIENT_CONNECT_WITH_DB = 0x00000008;
private static final int CLIENT_COMPRESS = 0x00000020; /* Can use compression protcol */
private static final int CLIENT_LOCAL_FILES = 0x00000080; /* Can use LOAD DATA LOCAL */
private static final int CLIENT_PROTOCOL_41 = 0x00000200; // for > 4.1.1
......

//MysqlIO构造函数
public MysqlIO(String host, int port, Properties props, String socketFactoryClassName, MySQLConnection conn, int socketTimeout, int useBufferRowSizeThreshold) throws IOException, SQLException {

    ......

    try {
        // 创建Socket对象,和MySql服务器建立连接  
        this.mysqlConnection = this.socketFactory.connect(this.host, this.port, props);

        if (socketTimeout != 0) {
            try {
                this.mysqlConnection.setSoTimeout(socketTimeout);
            } catch (Exception ex) {
                /* Ignore if the platform does not support it */
            }
        }
        // 获取Socket对象
        this.mysqlConnection = this.socketFactory.beforeHandshake();

        if (this.connection.getUseReadAheadInput()) {
            this.mysqlInput = new ReadAheadInputStream(this.mysqlConnection.getInputStream(), 16384, this.connection.getTraceProtocol(),
                    this.connection.getLog());
        } else if (this.connection.useUnbufferedInput()) {
            this.mysqlInput = this.mysqlConnection.getInputStream();
        } else {
            this.mysqlInput = new BufferedInputStream(this.mysqlConnection.getInputStream(), 16384);
        }

        //封装ScoketOutputStream输出流
        this.mysqlOutput = new BufferedOutputStream(this.mysqlConnection.getOutputStream(), 16384);

        this.isInteractiveClient = this.connection.getInteractiveClient();
        this.profileSql = this.connection.getProfileSql();
        this.autoGenerateTestcaseScript = this.connection.getAutoGenerateTestcaseScript();

        this.needToGrabQueryFromPacket = (this.profileSql || this.logSlowQueries || this.autoGenerateTestcaseScript);
        //封装SocketInputStream输入流  
        if (this.connection.getUseNanosForElapsedTime() && Util.nanoTimeAvailable()) {
            this.useNanosForElapsedTime = true;

            this.queryTimingUnits = Messages.getString("Nanoseconds");
        } else {
            this.queryTimingUnits = Messages.getString("Milliseconds");
        }

        if (this.connection.getLogSlowQueries()) {
            calculateSlowQueryThreshold();
        }
    } catch (IOException ioEx) {
        throw SQLError.createCommunicationsException(this.connection, 0, 0, ioEx, getExceptionInterceptor());
    }
}

0x02 总结

现在从头再梳理一下jdbc的注册过程。

第一步:

先看一下自己写的代码:

1

conn = DriverManager.getConnection("jdbc:mysql://192.168.108.145/test", "root", "root");

第二步:

getConnection方法中会获取到一个具体的connection。Connection con = aDriver.driver.connect(url, info);那么这个connection是怎么获取的呢,不同的厂商有自己特有的实现方式,对于mysql来说,在绕了一大圈后,最终调用了MysqlIO这个类来完成了和mysql-servier的连接。

第三步:

MysqlIO在获取到连接后,以NetworkResources的方式返回,这个NetworkResources就是mysql连接的一些基本信息。最后的连接由DriverManager的getConnection来获取。

第四步:

没了。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏钱塘大数据

理工男图解零维到十维空间,烧脑已过度,受不了啦!

让我们从一个点开始,和我们几何意义上的点一样,它没有大小、没有维度。它只是被想象出来的、作为标志一个位置的点。它什么也没有,空间、时间通通不存在,这就是零维度。

27230
来自专栏钱塘大数据

中国互联网协会发布:《2018中国互联网发展报告》

在2018中国互联网大会闭幕论坛上,中国互联网协会正式发布《中国互联网发展报告2018》(以下简称《报告》)。《中国互联网发展报告》是由中国互联网协会与中国互联...

13250
来自专栏腾讯高校合作

【倒计时7天】2018教育部-腾讯公司产学合作协同育人项目申请即将截止!

15320
来自专栏haifeiWu与他朋友们的专栏

复杂业务下向Mysql导入30万条数据代码优化的踩坑记录

从毕业到现在第一次接触到超过30万条数据导入MySQL的场景(有点low),就是在顺丰公司接入我司EMM产品时需要将AD中的员工数据导入MySQL中,因此楼主负...

26840
来自专栏怀英的自我修炼

考研英语-1-导学

英二图表作文要重视。总体而言,英语一会比英语二难点。不过就写作而言,英语二会比英语一有难度,毕竟图表作文并不好写。

11310
来自专栏FSociety

SQL中GROUP BY用法示例

GROUP BY我们可以先从字面上来理解,GROUP表示分组,BY后面写字段名,就表示根据哪个字段进行分组,如果有用Excel比较多的话,GROUP BY比较类...

5.1K20
来自专栏微信公众号:小白课代表

不只是软件,在线也可以免费下载百度文库了。

不管是学生,还是职场员工,下载各种文档几乎是不可避免的,各种XXX.docx,XXX.pptx更是家常便饭,人们最常用的就是百度文库,豆丁文库,道客巴巴这些下载...

43230
来自专栏前端桃园

知识体系解决迷茫的你

最近在星球里群里都有小伙伴说道自己对未来的路比较迷茫,一旦闲下来就不知道自己改干啥,今天我这篇文章就是让你觉得一天给你 25 个小时你都不够用,觉得睡觉都是浪费...

19440
来自专栏Ken的杂谈

【系统设置】CentOS 修改机器名

17330
来自专栏腾讯社交用户体验设计

ISUX Xcube智能一键生成H5

50820

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励