在前一篇中我们分析了驱动的加载,这篇我们看看数据库链接的获取即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>
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 可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串
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"); }
判断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; }
/** * 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; } }
/** * 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(); } } } }
// 链接的核心代码 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(); } }
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"); }