以我们熟悉的MysqlDriver来举例:
package com.mysql.jdbc;
import java.sql.SQLException;
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
//
// Register ourselves with the DriverManager
//
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()
}
}
在我们执行如下语句的时候,static块的内容会被执行,于是com.mysql.jdbc.Driver就成功的把自己给注册到DriverManager的驱动列表里面去了。
Class.forName("com.mysql.jdbc.Driver");
来看看DriverManager的注册实现:
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
public static synchronized void registerDriver(java.sql.Driver driver,
DriverAction da)
throws SQLException {
/* Register the driver if it has not already been added to our list */
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
代码的意思就是如果当前的Driver不存在就添加,否则就啥也不执行。
于是在DriverManager这个类里面就有了我们Mysql的驱动类了。
对于Oracle也是一样的,被加载的驱动都需要在被加载的时候,在static块中,自动把自己给注册到DriverManager中。
于是我们明白DriverManager就是维护了一个数据库的驱动列表,而且这个列表中同类型的数据库连接只有一份,比如我们系统里面即用到了mysql也用到了oracle那么我们的DriverManager里面只维护了2种类型的数据库驱动,不论我们实际上用了多个mysql数据库,驱动都是一样的。
看看DriverManager是如何获取数据库连接的:
第一步:构造用户信息
@CallerSensitive
public static Connection getConnection(String url,
String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}
return (getConnection(url, info, Reflection.getCallerClass()));
}
第二步:获取连接
// Worker method called by the public getConnection() methods.
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
// 线程同步,防止并发出问题
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}
println("DriverManager.getConnection(\"" + url + "\")");
SQLException reason = null;
// 循环当前的数据库驱动来获取数据库连接
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());
}
}
// 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");
}
对于上面的代码,我们不需要全部关注,只需要知道,连接的获取过程是通过循环已有的驱动,然后由每个驱动自己来完成的。我们来看看mysql的驱动实现:
public java.sql.Connection connect(String url, Properties info) throws SQLException {
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;
// 这个地方会判断当前url是不是属于mysql连接的前缀,不是就return
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;
} 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;
}
}
我们看看parseURL方法实现:
private static final String URL_PREFIX = "jdbc:mysql://";
@SuppressWarnings("deprecation")
public Properties parseURL(String url, Properties defaults) throws java.sql.SQLException {
Properties urlProps = (defaults != null) ? new Properties(defaults) : new Properties();
if (url == null) {
return null;
}
// 判断当前的url是不是以"jdbc:mysql://";开始
if (!StringUtils.startsWithIgnoreCase(url, URL_PREFIX) && !StringUtils.startsWithIgnoreCase(url, MXJ_URL_PREFIX)
&& !StringUtils.startsWithIgnoreCase(url, LOADBALANCE_URL_PREFIX) && !StringUtils.startsWithIgnoreCase(url, REPLICATION_URL_PREFIX)) {
return null;
}
...还有一大堆逻辑
return urlProps;
}
对于不同的数据库,因为使用的连接url不一样,比如mysql的连接格式如下
jdbc:mysql://localhost:3306/test?characterEncoding=utf-8
而oracle的连接字符串如下:
jdbc:oracle:thin:@127.0.0.1:1521:news
sql server的连接字符串如下:
jdbc:sqlserver://localhost:1434;DataBaseName=testdatabase
所以通过连接字符串的前缀不同可以区分出当前的驱动是不是目标驱动,如果不是,DriverManager接着循环下一个驱动来尝试获取连接。这样就可以通过DriverManager通过url来获取不同类型数据库的连接了。到此我们发现其实DriverManager维护的只是驱动而已,我们要获取那种类型数据库的连接,以及获取那个数据库连接还是取决于我们自己,因为获取数据库连接的时候,连接信息是我们自己指定的。
从上面的分析我们知道了,我们获取数据库的连接就是提供连接的url,用户名,密码就可以获取一个相应数据库的连接了,而如果要维护多个数据库连接,不就是提供多套url,用户名和密码吗?而如果你想手动的来把这些连接管理起来也很简单,其实就是如何管理多套数据库连接信息而已。