mysql jdbc驱动源码分析(获取链接 connection)

来源:互联网 发布:java能做网页游戏吗 编辑:程序博客网 时间:2024/04/29 05:38

在前一篇中我们分析了驱动的加载,这篇我们看看数据库链接的获取即Connection的获取,废话少说进入正题。

一、获取链接的方式有三种:


1、getConnection(String url,java.util.Properties info);

2、getConnection(String url,String user, String password);

3、getConnection(String url);

三种方式其中我们平时使用jdbc链接的时候用的最多的就是 1和3两种,下面我们首先来看看这三个方法的源码:

二、源码分析

1、getConnection(String url,java.util.Properties info) 方法的源码:

 

 public static Connection getConnection(String url,        java.util.Properties info) throws SQLException {        // Gets the classloader of the code that called this method, may        // be null.        ClassLoader callerCL = DriverManager.getCallerClassLoader();        return (getConnection(url, info, callerCL));    }
DriverManager调用方法获取类加载器。然后在调用getConnection(url,info,callerCL)方法获取Connection 链接,其中的<span style="font-family: Arial, Helvetica, sans-serif;">DriverManager.getCallerClassLoader()这个方法是native 方法。</span>

2、getConnection(String url,String user, String password) 方法源码:

 

 public static Connection getConnection(String url,        String user, String password) throws SQLException {        java.util.Properties info = new java.util.Properties();        // Gets the classloader of the code that called this method, may        // be null.        ClassLoader callerCL = DriverManager.getCallerClassLoader();        if (user != null) {            info.put("user", user);        }        if (password != null) {            info.put("password", password);        }        return (getConnection(url, info, callerCL));    }

同样首先调用方法获取了类加载器,其次将链接数据库的用户名密码添加到  Properties 中。

Properties 类表示了一个持久的属性集。Properties 可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串

3、getConnection(String url) 方法源码:

 public static Connection getConnection(String url)        throws SQLException {        java.util.Properties info = new java.util.Properties();        // Gets the classloader of the code that called this method, may        // be null.        ClassLoader callerCL = DriverManager.getCallerClassLoader();        return (getConnection(url, info, callerCL));    }

通过这三个源码我们可以看到主要的有两个,一个是获取列加载器的 getCallerClassLoader() 方法一个是 getConnection(url, info, callerCL) 获取链接的方法,下面来看看getConnection(url, info, callerCL)  方法的源码:


//  Worker method called by the public getConnection() methods.// 实际是通过这个方法来获取connection的// 通过之前的方法我们可以看到,所有的方法都调用了这个方法即请求地址,properties 和类加载器    private static Connection getConnection(        String url, java.util.Properties info, ClassLoader callerCL) throws SQLException {        /*         * When callerCl is null, we should check the application's         * (which is invoking this class indirectly)         * classloader, so that the JDBC driver class outside rt.jar         * can be loaded from here.         */ // 同步代码块        synchronized(DriverManager.class) {          // synchronize loading of the correct classloader.          if(callerCL == null) {  // 如果类加载器是null 则获取当前线程中的类加载器              callerCL = Thread.currentThread().getContextClassLoader();           }        }        if(url == null) {            throw new SQLException("The url cannot be null", "08001");        }        println("DriverManager.getConnection(\"" + url + "\")");        // Walk through the loaded registeredDrivers attempting to make a connection.        // Remember the first exception that gets raised so we can reraise it.        SQLException reason = null;       // 这里的这个集合就是 刚开始 调用resigerDriver方法将加载的驱动存放到这个集合中        for(DriverInfo aDriver : registeredDrivers) {            // If the caller does not have permission to load the driver then            // skip it.// 调用isDriverAllowed 这个方法判断是不是同一个类,即Class的对象是不是相等。            if(isDriverAllowed(aDriver.driver, callerCL)) {                try {                    println("    trying " + aDriver.driver.getClass().getName());// 获取链接,这里调用了Driver的父类的方法。                    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());            }        }        // if we got here nobody could connect.        if (reason != null)    {            println("getConnection failed: " + reason);            throw reason;        }        println("getConnection: no suitable driver found for "+ url);        throw new SQLException("No suitable driver found for "+ url, "08001");    }

这个方法判断驱动如果驱动类相等则调用Driver的父类进行和数据库的链接如果不相等则抛出异常链接结束。

判断Class 的类型的对象是不是相等的方法源码如下:

   private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {        boolean result = false;        if(driver != null) {            Class<?> aClass = null;            try { // 这里正好用到了反射的两个东西我们来看看// object.getClass() 获取对象的类型即Class类的对象,通过调用Class类提供的方法可以获取这个类的类名称(包名+类名)以及类加载器。//  使用给定的类加载器,返回与带有给定字符串名的类或接口相关联的 Class 对象。                aClass =  Class.forName(driver.getClass().getName(), true, classLoader);            } catch (Exception ex) {                result = false;            }        //比较两个Class 的实例是不是相等,其实就是判断 是不是同一个类即:包名+类名+加载器名称,只要类名获取类加载器不同就不是同一个类。             result = ( aClass == driver.getClass() ) ? true : false;        }        return result;    }

ok 到这里该判断的都判断了,该比较的都比较了,现在我们看看 Driver的父类的conect方法,源代码如下:

    /**     * Try to make a database connection to the given URL. The driver should return "null" if it realizes it is the wrong kind of driver to connect to the given     * URL. This will be common, as when the JDBC driverManager is asked to connect to a given URL, it passes the URL to each loaded driver in turn.     *      * <p>     * The driver should raise an SQLException if the URL is null or if it is the right driver to connect to the given URL, but has trouble connecting to the     * database.     * </p>     *      * <p>     * The java.util.Properties argument can be used to pass arbitrary string tag/value pairs as connection arguments. These properties take precedence over any     * properties sent in the URL.     * </p>     *      * <p>     * MySQL protocol takes the form:     *      * <PRE>     * jdbc:mysql://host:port/database     * </PRE>     *      * </p>     *      * @param url     *            the URL of the database to connect to     * @param info     *            a list of arbitrary tag/value pairs as connection arguments     *      * @return a connection to the URL or null if it isn't us     *      * @exception SQLException     *                if a database access error occurs or the url is null     *      * @see java.sql.Driver#connect     */ // 获取给定url的数据库的链接,inro 就是用户名和密码等参数,如果url 为null 则会返回一个异常信息。    public java.sql.Connection connect(String url, Properties info) throws SQLException {// url: jdbc:mysql://ip:port/dbname        if (url == null) {            throw SQLError.createSQLException(Messages.getString("NonRegisteringDriver.1"), SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE, null);        }        // 判断url前缀是不是满足url的格式        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;         // parseUrl(url,info)方法解析url ,用户名密码以及其他的参数,并添加到Properties集合中        if ((props = parseURL(url, info)) == null) {            return null;        }        if (!"1".equals(props.getProperty(NUM_HOSTS_PROPERTY_KEY))) {            return connectFailover(url, info);        }        try {// 这里获取具体的链接 类是ConnectionImpl// 其中调用的host port database 方法获取对应的参数的值            Connection newConn = com.mysql.jdbc.ConnectionImpl.getInstance(host(props), port(props), props, database(props), url);            return newConn;        } catch (SQLException sqlEx) {            // Don't wrap SQLExceptions, throw            // them un-changed.            throw sqlEx;        } catch (Exception ex) {            SQLException sqlEx = SQLError.createSQLException(                    Messages.getString("NonRegisteringDriver.17") + ex.toString() + Messages.getString("NonRegisteringDriver.18"),                    SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE, null);            sqlEx.initCause(ex);            throw sqlEx;        }    }

我们据需跟进 com.mysql.jdbc.ConnectionImpl.getInstance(host(props), port(props), props, database(props), url);这个方法,源码如下:


 /**     * Creates a connection instance -- We need to provide factory-style methods     * so we can support both JDBC3 (and older) and JDBC4 runtimes, otherwise     * the class verifier complains when it tries to load JDBC4-only interface     * classes that are present in JDBC4 method signatures.     */         // 参数有: 主机  端口号  properties  database url     protected static Connection getInstance(String hostToConnectTo, int portToConnectTo, Properties info, String databaseToConnectTo, String url)            throws SQLException {// 调用util 类的方法判断,驱动类是否能够找到// 创建ConnectionImpl 对象。        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);    }

ConnectionImpl的构造函数来创建一个Connection实例,即向mysql服务获取链接:
<pre name="code" class="java">/**     * Creates a connection to a MySQL Server.     *      * @param hostToConnectTo     *            the hostname of the database server     * @param portToConnectTo     *            the port number the server is listening on     * @param info     *            a Properties[] list holding the user and password     * @param databaseToConnectTo     *            the database to connect to     * @param url     *            the URL of the connection     * @param d     *            the Driver instantation of the connection     * @exception SQLException     *                if a database access error occurs     */ // 构造函数来创建链接实例    public ConnectionImpl(String hostToConnectTo, int portToConnectTo, Properties info, String databaseToConnectTo, String url) throws SQLException {        this.connectionCreationTimeMillis = System.currentTimeMillis();        if (databaseToConnectTo == null) {            databaseToConnectTo = "";        }        // Stash away for later, used to clone this connection for Statement.cancel and Statement.setQueryTimeout().        //        this.origHostToConnectTo = hostToConnectTo;  // host         this.origPortToConnectTo = portToConnectTo;  //port        this.origDatabaseToConnectTo = databaseToConnectTo;  //数据库名        try {            Blob.class.getMethod("truncate", new Class[] { Long.TYPE });            this.isRunningOnJDK13 = false;        } catch (NoSuchMethodException nsme) {            this.isRunningOnJDK13 = true;        }        this.sessionCalendar = new GregorianCalendar();        this.utcCalendar = new GregorianCalendar();        this.utcCalendar.setTimeZone(TimeZone.getTimeZone("GMT"));        //        // Normally, this code would be in initializeDriverProperties, but we need to do this as early as possible, so we can start logging to the 'correct'        // place as early as possible...this.log points to 'NullLogger' for every connection at startup to avoid NPEs and the overhead of checking for NULL at        // every logging call.        //        // We will reset this to the configured logger during properties initialization.        //        this.log = LogFactory.getLogger(getLogger(), LOGGER_INSTANCE_NAME, getExceptionInterceptor());        this.openStatements = new HashMap<Statement, Statement>();        if (NonRegisteringDriver.isHostPropertiesList(hostToConnectTo)) {            Properties hostSpecificProps = NonRegisteringDriver.expandHostKeyValues(hostToConnectTo);            Enumeration<?> propertyNames = hostSpecificProps.propertyNames();            while (propertyNames.hasMoreElements()) {                String propertyName = propertyNames.nextElement().toString();                String propertyValue = hostSpecificProps.getProperty(propertyName);                info.setProperty(propertyName, propertyValue);            }        } else {            if (hostToConnectTo == null) {                this.host = "localhost";                this.hostPortPair = this.host + ":" + portToConnectTo;            } else {                this.host = hostToConnectTo;                if (hostToConnectTo.indexOf(":") == -1) {                    this.hostPortPair = this.host + ":" + portToConnectTo;                } else {                    this.hostPortPair = this.host;                }            }        }       // 获取了所有链接数据库需要的参数        this.port = portToConnectTo;        this.database = databaseToConnectTo;        this.myURL = url;        this.user = info.getProperty(NonRegisteringDriver.USER_PROPERTY_KEY);        this.password = info.getProperty(NonRegisteringDriver.PASSWORD_PROPERTY_KEY);        if ((this.user == null) || this.user.equals("")) {            this.user = "";        }        if (this.password == null) {            this.password = "";        }        this.props = info;        initializeDriverProperties(info);        // We store this per-connection, due to static synchronization issues in Java's built-in TimeZone class...        this.defaultTimeZone = TimeUtil.getDefaultTimeZone(getCacheDefaultTimezone());        this.isClientTzUTC = !this.defaultTimeZone.useDaylightTime() && this.defaultTimeZone.getRawOffset() == 0;        if (getUseUsageAdvisor()) {            this.pointOfOrigin = LogUtils.findCallingClassAndMethod(new Throwable());        } else {            this.pointOfOrigin = "";        }        try {            this.dbmd = getMetaData(false, false);// 进行数据库的链接            initializeSafeStatementInterceptors();// 创建io流            createNewIO(false);//            unSafeStatementInterceptors();        } catch (SQLException ex) {            cleanup(ex);            // don't clobber SQL exceptions            throw ex;        } catch (Exception ex) {            cleanup(ex);            StringBuilder mesg = new StringBuilder(128);            if (!getParanoid()) {                mesg.append("Cannot connect to MySQL server on ");                mesg.append(this.host);                mesg.append(":");                mesg.append(this.port);                mesg.append(".\n\n");                mesg.append("Make sure that there is a MySQL server ");                mesg.append("running on the machine/port you are trying ");                mesg.append("to connect to and that the machine this software is running on ");                mesg.append("is able to connect to this host/port (i.e. not firewalled). ");                mesg.append("Also make sure that the server has not been started with the --skip-networking ");                mesg.append("flag.\n\n");            } else {                mesg.append("Unable to connect to database.");            }            SQLException sqlEx = SQLError.createSQLException(mesg.toString(), SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE, getExceptionInterceptor());            sqlEx.initCause(ex);            throw sqlEx;        }        NonRegisteringDriver.trackConnection(this);    }


我们发现具体的链接没有在这个方法中实现,而是在createNewIO()这个方法中实现的,具体代码如下:

ConnectionImpl 类中的createNewIO() 方法如下:

  /**     * Creates an IO channel to the server     *      * @param isForReconnect     *            is this request for a re-connect     * @return a new MysqlIO instance connected to a server     * @throws SQLException     *             if a database access error occurs     * @throws CommunicationsException     */ // 创建io流,在调用这个方法的时候传的参数是false    public void createNewIO(boolean isForReconnect) throws SQLException {// 同步代码块        synchronized (getConnectionMutex()) {            // Synchronization Not needed for *new* connections, but defintely for connections going through fail-over, since we might get the new connection up            // and running *enough* to start sending cached or still-open server-side prepared statements over to the backend before we get a chance to            // re-prepare them...            Properties mergedProps = exposeAsProperties(this.props);            if (!getHighAvailability()) {                connectOneTryOnly(isForReconnect, mergedProps);                return;            }             // 调用方法尝试多次链接            connectWithRetries(isForReconnect, mergedProps);        }    }
调用ConnectionImpl 类的connectWithRetries方法进行多次的尝试链接:

// 链接的具体实现    private void connectWithRetries(boolean isForReconnect, Properties mergedProps) throws SQLException {        double timeout = getInitialTimeout();        boolean connectionGood = false;        Exception connectionException = null;        // 在没有获的链接的情况下尝试链接多次,知道最大尝试次数        for (int attemptCount = 0; (attemptCount < getMaxReconnects()) && !connectionGood; attemptCount++) {            try {                if (this.io != null) {                    this.io.forceClose();                }                // 调用这个方法进行链接获取                coreConnect(mergedProps);                pingInternal(false, 0);                boolean oldAutoCommit;                int oldIsolationLevel;                boolean oldReadOnly;                String oldCatalog;                synchronized (getConnectionMutex()) {                    this.connectionId = this.io.getThreadId();                    this.isClosed = false;                    // save state from old connection                    oldAutoCommit = getAutoCommit();                    oldIsolationLevel = this.isolationLevel;                    oldReadOnly = isReadOnly(false);                    oldCatalog = getCatalog();                    this.io.setStatementInterceptors(this.statementInterceptors);                }                // Server properties might be different from previous connection, so initialize again...                initializePropsFromServer();                if (isForReconnect) {                    // Restore state from old connection                    setAutoCommit(oldAutoCommit);                    if (this.hasIsolationLevels) {                        setTransactionIsolation(oldIsolationLevel);                    }                    setCatalog(oldCatalog);                    setReadOnly(oldReadOnly);                }                connectionGood = true;                break;            } catch (Exception EEE) {                connectionException = EEE;                connectionGood = false;            }            if (connectionGood) {                break;            }            if (attemptCount > 0) {                try {                    Thread.sleep((long) timeout * 1000);                } catch (InterruptedException IE) {                    // ignore                }            }        } // end attempts for a single host        if (!connectionGood) {            // We've really failed!            SQLException chainedEx = SQLError.createSQLException(                    Messages.getString("Connection.UnableToConnectWithRetries", new Object[] { Integer.valueOf(getMaxReconnects()) }),                    SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE, getExceptionInterceptor());            chainedEx.initCause(connectionException);            throw chainedEx;        }        if (getParanoid() && !getHighAvailability()) {            this.password = null;            this.user = null;        }        if (isForReconnect) {            //            // Retrieve any 'lost' prepared statements if re-connecting            //            Iterator<Statement> statementIter = this.openStatements.values().iterator();            //            // We build a list of these outside the map of open statements, because in the process of re-preparing, we might end up having to close a prepared            // statement, thus removing it from the map, and generating a ConcurrentModificationException            //            Stack<Statement> serverPreparedStatements = null;            while (statementIter.hasNext()) {                Statement statementObj = statementIter.next();                if (statementObj instanceof ServerPreparedStatement) {                    if (serverPreparedStatements == null) {                        serverPreparedStatements = new Stack<Statement>();                    }                    serverPreparedStatements.add(statementObj);                }            }            if (serverPreparedStatements != null) {                while (!serverPreparedStatements.isEmpty()) {                    ((ServerPreparedStatement) serverPreparedStatements.pop()).rePrepare();                }            }        }    }

ConnectionImpl类中获取链接I/O流的核心代码:

    // 链接的核心代码    private void coreConnect(Properties mergedProps) throws SQLException, IOException {        // 默认端口号int newPort = 3306;// 默认主机名 localhost        String newHost = "localhost";        // 协议        String protocol = mergedProps.getProperty(NonRegisteringDriver.PROTOCOL_PROPERTY_KEY);        if (protocol != null) {            // "new" style URL            if ("tcp".equalsIgnoreCase(protocol)) {                newHost = normalizeHost(mergedProps.getProperty(NonRegisteringDriver.HOST_PROPERTY_KEY));                newPort = parsePortNumber(mergedProps.getProperty(NonRegisteringDriver.PORT_PROPERTY_KEY, "3306"));            } else if ("pipe".equalsIgnoreCase(protocol)) {                setSocketFactoryClassName(NamedPipeSocketFactory.class.getName());                String path = mergedProps.getProperty(NonRegisteringDriver.PATH_PROPERTY_KEY);                if (path != null) {                    mergedProps.setProperty(NamedPipeSocketFactory.NAMED_PIPE_PROP_NAME, path);                }            } else {                // normalize for all unknown protocols                newHost = normalizeHost(mergedProps.getProperty(NonRegisteringDriver.HOST_PROPERTY_KEY));                newPort = parsePortNumber(mergedProps.getProperty(NonRegisteringDriver.PORT_PROPERTY_KEY, "3306"));            }        } else {            String[] parsedHostPortPair = NonRegisteringDriver.parseHostPortPair(this.hostPortPair);            newHost = parsedHostPortPair[NonRegisteringDriver.HOST_NAME_INDEX];            newHost = normalizeHost(newHost);            if (parsedHostPortPair[NonRegisteringDriver.PORT_NUMBER_INDEX] != null) {                newPort = parsePortNumber(parsedHostPortPair[NonRegisteringDriver.PORT_NUMBER_INDEX]);            }        }        this.port = newPort;        this.host = newHost;        // reset max-rows to default value        this.sessionMaxRows = -1;        // io创建获取和mysql 服务器的链接  // 方法getSocketFactoryClassName() 获取类名   /** The I/O abstraction interface (network conn to MySQL server */   // 获得和mysql服务的链接        this.io = new MysqlIO(newHost, newPort, mergedProps, getSocketFactoryClassName(), getProxy(), getSocketTimeout(),                this.largeRowSizeThreshold.getValueAsInt());        this.io.doHandshake(this.user, this.password, this.database);        if (versionMeetsMinimum(5, 5, 0)) {            // error messages are returned according to character_set_results which, at this point, is set from the response packet            this.errorMessageEncoding = this.io.getEncodingForHandshake();        }    }

到这里我们发现创建了一个MysqlIO 类,从这个类中获取了io流,而具体的socket 是调用了socketFactory 这个接口的实现类StandardSocketFactory 这个类的实例的connect 方法获取了一个指定的IP ,Port的socket 链接。

StandardSocketFactory类的connect 方法如下:


 /**     * @see com.mysql.jdbc.SocketFactory#createSocket(Properties)     */    public Socket connect(String hostname, int portNumber, Properties props) throws SocketException, IOException {        if (props != null) {            this.host = hostname;            this.port = portNumber;            String localSocketHostname = props.getProperty("localSocketAddress");            InetSocketAddress localSockAddr = null;            if (localSocketHostname != null && localSocketHostname.length() > 0) {                localSockAddr = new InetSocketAddress(InetAddress.getByName(localSocketHostname), 0);            }            String connectTimeoutStr = props.getProperty("connectTimeout");            int connectTimeout = 0;            if (connectTimeoutStr != null) {                try {                    connectTimeout = Integer.parseInt(connectTimeoutStr);                } catch (NumberFormatException nfe) {                    throw new SocketException("Illegal value '" + connectTimeoutStr + "' for connectTimeout");                }            }            if (this.host != null) {                InetAddress[] possibleAddresses = InetAddress.getAllByName(this.host);                if (possibleAddresses.length == 0) {                    throw new SocketException("No addresses for host");                }                // save last exception to propagate to caller if connection fails                SocketException lastException = null;                // Need to loop through all possible addresses. Name lookup may return multiple addresses including IPv4 and IPv6 addresses. Some versions of                // MySQL don't listen on the IPv6 address so we try all addresses.                for (int i = 0; i < possibleAddresses.length; i++) {                    try {                        this.rawSocket = createSocket(props);                        configureSocket(this.rawSocket, props);                        InetSocketAddress sockAddr = new InetSocketAddress(possibleAddresses[i], this.port);                        // bind to the local port if not using the ephemeral port                        if (localSockAddr != null) {                            this.rawSocket.bind(localSockAddr);                        }                        this.rawSocket.connect(sockAddr, getRealTimeout(connectTimeout));                        break;                    } catch (SocketException ex) {                        lastException = ex;                        resetLoginTimeCountdown();                        this.rawSocket = null;                    }                }                if (this.rawSocket == null && lastException != null) {                    throw lastException;                }                resetLoginTimeCountdown();                return this.rawSocket;            }        }        throw new SocketException("Unable to create socket");    }

到这里发现通过mysql 实现驱动的加载获取 驱动类实例后通过各种判断最终就是获取了指定IP,PORT的socket 的获取。获得Socket链接后我们也就获的了和mysql server的链接。




1 0