以我们熟悉的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
所以通过连接字符串的前缀不同可以区分出当前的驱动是不是目标驱动,如果不是,DriverManager接着循环下一个驱动来尝试获取连接。这样就可以通过DriverManager通过url来获取不同类型数据库的连接了。到此我们发现其实 DriverManager维护的只是驱动而已 ,我们要获取那种类型数据库的连接,以及获取那个数据库连接还是取决于我们自己,因为获取数据库连接的时候, 连接信息是我们自己指定的 。
从上面的分析我们知道了,我们获取数据库的连接就是提供连接的url,用户名,密码就可以获取一个相应数据库的连接了,而如果要维护多个数据库连接,不就是提供多套url,用户名和密码吗?而如果你想手动的来把这些连接管理起来也很简单,其实就是如何管理多套数据库连接信息而已。举例如下:
有2个数据库:jdbc:mysql://localhost:3306/test 和 jdbc:mysql://localhost:3306/demo
CREATE TABLE `user` ( `id` int(20) NOT NULL AUTO_INCREMENT, `username` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, `password` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
id | username | password |
---|---|---|
1 | u3 | p3 |
id | username | password |
---|---|---|
1 | u1 | p1 |
2 | u2 | p2 |
这个只是简单的使用map来维护了我们多个数据源,你完全可以把它改造为自己想要的那种方式,比如主从结构的数据库…,当然了我们这里这么做并不是非要自己维护这些数据源,只是让你知道多数据源维护的原理,而真正多数据源我们是使用相应的框架来实现的
package com.bsx.test; import lombok.Data; import org.junit.Test; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; import java.util.HashMap; import java.util.Map; /** * @Description: 模拟多数据源管理 * @author: ztd * @date 2019/7/8 下午4:41 */ public class MultiConnTest { /** * 多数据源处理 * 1.insert使用一个数据源 * 2.query使用另一个数据源 * * @throws Exception */ @Test public void testMultiDB() throws Exception { DBConf test = new DBConf("root", "12345678", "jdbc:mysql://localhost:3306/test?characterEncoding=utf-8"); DBConf demo = new DBConf("root", "12345678", "jdbc:mysql://localhost:3306/demo?characterEncoding=utf-8"); Map<String, DBConf> dbConfMap = new HashMap<>(); dbConfMap.put("test", test); dbConfMap.put("demo", demo); Connection connection = getConn(dbConfMap.get("test")); System.out.println("======print test user info======"); printUserInfo(connection); connection = getConn(dbConfMap.get("demo")); System.out.println("======print demo user info======"); printUserInfo(connection); } public static void printUserInfo(Connection connection) throws Exception { Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery("SELECT * FROM user"); while (resultSet.next()) { System.out.println("id:" +resultSet.getInt(1) + " name: " + resultSet.getString(2) + " password: " + resultSet.getString(3)); } resultSet.close(); statement.close(); connection.close(); } public static Connection getConn(DBConf dbConf) { return initMysql(dbConf.getUrl(), dbConf.getUser(), dbConf.getPassword()); } /** * @description 连接mysql * @author ztd * @date 2019/7/8 下午5:06 */ public static Connection initMysql(String url, String user, String password) { Connection conn = null; try{ //jdbc:数据库类型://主机IP:端口/数据库名?characterEncoding=编码 Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection(url, user, password); }catch(Exception e){ System.out.println("数据库连接异常!"); e.printStackTrace(); } return conn; } @Data class DBConf { private String user; private String password; private String url; public DBConf(String user, String password, String url) { this.user = user; this.password = password; this.url = url; } } }
运行结果:
======print test user info====== id:1 name: u3 password: p3 ======print demo user info====== id:1 name: u1 password: p1 id:2 name: u2 password: p2