最近在学习mybatis的源码,有一个databaseIdProvider
根据不同数据库执行不同sql的功能,我正好有一个mysql还有一个瀚高数据库,就去试了一下,使用如下
pom文件导入两个数据库的驱动
xml
mysql mysql-connector-java 8.0.13 XML 复制 全屏 com.highgo HgdbJdbc 6.2.2
主启动类.java
java
public class MybatisHelloWorld { public static void main(String[] args) throws Exception { String resource = "org/mybatis/config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession session = sqlSessionFactory.openSession(); UserMapper mapper = session.getMapper(UserMapper.class); Listusers = mapper.getUsers(1); session.close(); } }
User.java
java
public class User { private Integer id; private String name; private Integer age; //....getter setter 构造.. }
UserMapper.java
java
public interface UserMapper { ListgetUsers(int age); }
UserMapper.xml
xml
Mybatis配置文件
xml
当我把mybatis配置文件中的环境设置为
,代码执行结果如下
然后修改环境设置为
后,代码执行结果如下
不知道您有没有看出问题所在,在上面的mybatis配置文件中highgo环境的驱动是com.mysql.cj.jdbc.Driver
但是能连接上瀚高的数据库并且能正常执行sql
当时我也发现这个问题了,于是想研究下原因
首先要找到是哪一段代码进行的操作,那么这里肯定是创建连接的时候,因为驱动不对的话是连接不上的,于是跟着这个思路就去寻找
最后找到方法栈如下
UnpooledDataSource.java
java
private Connection doGetConnection(Properties properties) throws SQLException { initializeDriver(); Connection connection = DriverManager.getConnection(url, properties); configureConnection(connection); return connection; }
java
private synchronized void initializeDriver() throws SQLException { //判断这个驱动是否注册过 if (!registeredDrivers.containsKey(driver)) { Class> driverType; try { if (driverClassLoader != null) { driverType = Class.forName(driver, true, driverClassLoader); } else { driverType = Resources.classForName(driver); } Driver driverInstance = (Driver)driverType.newInstance(); DriverManager.registerDriver(new DriverProxy(driverInstance)); registeredDrivers.put(driver, driverInstance); } catch (Exception e) { throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e); } } }
先判断需要加载的驱动是否已经注册了
那这里面的两个驱动是从哪里来的呢?
就在这个UnpooledDataSource类中的静态块里面
java
static { Enumerationdrivers = DriverManager.getDrivers(); while (drivers.hasMoreElements()) { Driver driver = drivers.nextElement(); registeredDrivers.put(driver.getClass().getName(), driver); } }
而DriverManager中有一个集合用来存储所有已经注册的数据库连接驱动
java
public class DriverManager { // List of registered JDBC drivers private final static CopyOnWriteArrayListregisteredDrivers = new CopyOnWriteArrayList<>(); //.... public static java.util.Enumeration getDrivers() { java.util.Vector result = new java.util.Vector<>(); Class> callerClass = Reflection.getCallerClass(); // Walk through the loaded registeredDrivers. for(DriverInfo aDriver : registeredDrivers) { // If the caller does not have permission to load the driver then // skip it. if(isDriverAllowed(aDriver.driver, callerClass)) { result.addElement(aDriver.driver); } else { println(" skipping: " + aDriver.getClass().getName()); } } return (result.elements()); } //...... }
那么问题又来了,DriverManager里面的瀚高数据库驱动啥时候放进去的呢
在学java基础的jdbc时,肯定都写过类似这样的代码
java
public static void main(String[] args) throws Exception { Class.forName("com.mysql.cj.jdbc.Driver"); Connection con= DriverManager.getConnection("jdbc:mysql://localhost:3306/xxx","root","XXXXXX"); Statement stat=con.createStatement(); //...... }
当时这段Class.forName("com.mysql.cj.jdbc.Driver");
就告诉你是加载驱动,有的博客写了这段代码,有的没写,具体操作一直都不清楚
首先JDK5版本以后可以不用显式调用这段话,DriverManager会自己去加载合适的驱动,前提是这个驱动存在于CLASSPATH下
其次,它是怎么加载的呢?为啥Class.forName就能加载呢?
当一个类被加载到JVM时会执行静态代码块,我们以mysql的驱动举例子
java
package com.mysql.cj.jdbc; import java.sql.DriverManager; import java.sql.SQLException; public class Driver extends NonRegisteringDriver implements java.sql.Driver { public Driver() throws SQLException { } static { try { DriverManager.registerDriver(new Driver()); } catch (SQLException var1) { throw new RuntimeException("Can't register driver!"); } } }
所以最终调用的还是DriverManager.registerDriver(new Driver());
注册一个驱动,底层就是放入到registeredDrivers这个集合中
以瀚高的数据库驱动来看,当调用DriverManager.getDrivers
时,DriverManager会去加载驱动类,继而驱动类执行static代码块
最终还是使用DriverManager.registerDriver
注册了瀚高的数库驱动
那么回到UnpooledDataSource类中
java
public class UnpooledDataSource implements DataSource { private static MapregisteredDrivers = new ConcurrentHashMap (); //..... static { //这里就会获取到mysql和瀚高的驱动 Enumeration drivers = DriverManager.getDrivers(); while (drivers.hasMoreElements()) { Driver driver = drivers.nextElement(); registeredDrivers.put(driver.getClass().getName(), driver); } } //..... private Connection doGetConnection(Properties properties) throws SQLException { initializeDriver(); Connection connection = DriverManager.getConnection(url, properties); configureConnection(connection); return connection; } }
initializeDriver()
加载一些其他的驱动,例如我们自定义一个类,实现Driver接口,然后在
使用
那么Connection connection = DriverManager.getConnection(url, properties);
不就是基础的JDBC连接数据库的操作吗
现在还有一个问题,DriverManager是怎么确定使用哪个数据库驱动呢
DriverManager.java
java
private static Connection getConnection( //...... for(DriverInfo aDriver : registeredDrivers) { //检查是否能加载这个驱动到jvm,不能就跳过,底层使用Class.forName 没出异常就是能加载 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()); } } //..... }
底层也很简单,就是遍历驱动集合,每个驱动都去连接一下数据库,如果能连接上说明这个驱动是对的,返回这个驱动创建的连接
也解答了我自己以前的疑惑和错误的理解
写给哪个环境,哪个环境就使用这个驱动