前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Jdbc源码详解(二):获取connection

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

作者头像
木东居士
发布2018-05-25 14:27:41
2.3K2
发布2018-05-25 14:27:41
举报

0x00 前言

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

0x01 connection的建立过程

DriverManager.getConnection 做了什么

代码语言:javascript
复制
conn = DriverManager.getConnection("jdbc:mysql://192.168.108.145/test", "root", "root");

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

代码语言:javascript
复制
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中了。

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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,因此在构造的时候也会先调用父类的构造器。

代码语言:javascript
复制
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)

代码语言:javascript
复制
public ConnectionImpl(String hostToConnectTo, int portToConnectTo, Properties info, String databaseToConnectTo, String url) throws SQLException {
    ......
    NonRegisteringDriver.trackConnection(this);
}

com.mysql.jdbc.NonRegisteringDriver

代码语言:javascript
复制
protected static void trackConnection(Connection newConn) {
    ConnectionPhantomReference phantomRef = new ConnectionPhantomReference((ConnectionImpl) newConn, refQueue);
    connectionPhantomRefs.put(phantomRef, phantomRef);
}

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

代码语言:javascript
复制
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

代码语言:javascript
复制
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.

代码语言:javascript
复制
//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来获取。

第四步:

没了。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016-06-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0x00 前言
  • 0x01 connection的建立过程
  • 0x02 总结
相关产品与服务
云数据库 MySQL
腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档