Mybatis資料庫驅動
最近在學習mybatis的原始碼,有一個databaseIdProvider
根據不同資料庫執行不同sql的功能,我正好有一個mysql還有一個瀚高資料庫,就去試了一下,使用如下
pom檔案匯入兩個資料庫的驅動
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
<dependency>
<groupId>com.highgo</groupId>
<artifactId>HgdbJdbc</artifactId>
<version>6.2.2</version>
</dependency>
主啟動類.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);
List<User> users = mapper.getUsers(1);
session.close();
}
}
User.java
public class User {
private Integer id;
private String name;
private Integer age;
//....getter setter 構造..
}
UserMapper.java
public interface UserMapper {
List<User> getUsers(int age);
}
UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.mapper.UserMapper" >
<select id="getUsers" resultType="org.mybatis.pojo.User" databaseId="mysql">
select * from user where age = #{age}
</select>
<select id="getUsers" resultType="org.mybatis.pojo.User" databaseId="postgresql">
select * from mybatis.user where age = #{age}
</select>
</mapper>
Mybatis配置檔案
<configuration>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
<environment id="highgo">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:highgo://localhost:5866/highgo?currentSchema=mybatis"/>
<property name="username" value="highgo"/>
<property name="password" value="Hello@123"/>
</dataSource>
</environment>
</environments>
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql"/>
<property name="PostgreSQL" value="postgresql"/>
</databaseIdProvider>
<mappers>
<package name="org.mybatis.mapper"/>
</mappers>
</configuration>
當我把mybatis配置檔案中的環境設定為<environments default="mysql">
,程式碼執行結果如下
然後修改環境設定為<environments default="highgo">
後,程式碼執行結果如下
不知道您有沒有看出問題所在,在上面的mybatis配置檔案中highgo環境的驅動是com.mysql.cj.jdbc.Driver
但是能連線上瀚高的資料庫並且能正常執行sql
當時我也發現這個問題了,於是想研究下原因
首先要找到是哪一段程式碼進行的操作,那麼這裡肯定是建立連線的時候,因為驅動不對的話是連線不上的,於是跟著這個思路就去尋找
最後找到方法棧如下
- doGetConnection:200, UnpooledDataSource (org.apache.ibatis.datasource.unpooled)
- doGetConnection:196, UnpooledDataSource (org.apache.ibatis.datasource.unpooled)
- getConnection:93, UnpooledDataSource (org.apache.ibatis.datasource.unpooled)
- popConnection:407, PooledDataSource (org.apache.ibatis.datasource.pooled)
- getConnection:89, PooledDataSource (org.apache.ibatis.datasource.pooled)
- getDatabaseProductName:82, VendorDatabaseIdProvider (org.apache.ibatis.mapping)
- getDatabaseName:66, VendorDatabaseIdProvider (org.apache.ibatis.mapping)
- getDatabaseId:53, VendorDatabaseIdProvider (org.apache.ibatis.mapping)
- databaseIdProviderElement:305, XMLConfigBuilder (org.apache.ibatis.builder.xml)
- parseConfiguration:123, XMLConfigBuilder (org.apache.ibatis.builder.xml)
- parse:97, XMLConfigBuilder (org.apache.ibatis.builder.xml)
- build:82, SqlSessionFactoryBuilder (org.apache.ibatis.session)
- build:67, SqlSessionFactoryBuilder (org.apache.ibatis.session)
- main:32, MybatisHelloWorld (org.mybatis)
UnpooledDataSource.java
private Connection doGetConnection(Properties properties) throws SQLException {
initializeDriver();
Connection connection = DriverManager.getConnection(url, properties);
configureConnection(connection);
return connection;
}
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類中的靜態塊裡面
static {
Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
Driver driver = drivers.nextElement();
registeredDrivers.put(driver.getClass().getName(), driver);
}
}
而DriverManager中有一個集合用來儲存所有已經註冊的資料庫連線驅動
public class DriverManager {
// List of registered JDBC drivers
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
//....
public static java.util.Enumeration<Driver> getDrivers() {
java.util.Vector<Driver> 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時,肯定都寫過類似這樣的程式碼
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的驅動舉例子
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類中
public class UnpooledDataSource implements DataSource {
private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<String, Driver>();
//.....
static {
//這裡就會獲取到mysql和瀚高的驅動
Enumeration<Driver> 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介面,然後在<property name="driver" value="com.drive.MyDrive"/>
使用
那麼Connection connection = DriverManager.getConnection(url, properties);
不就是基礎的JDBC連線資料庫的操作嗎
現在還有一個問題,DriverManager是怎麼確定使用哪個資料庫驅動呢
DriverManager.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());
}
}
//.....
}
底層也很簡單,就是遍歷驅動集合,每個驅動都去連線一下資料庫,如果能連線上說明這個驅動是對的,返回這個驅動建立的連線
也解答了我自己以前的疑惑和錯誤的理解
- 一直不清楚Class.forName("xxx.Driver")是怎麼載入驅動的
- 以為mybatis配置檔案中的
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
寫給哪個環境,哪個環境就使用這個驅動
現在是明白了