前言
我们知道数据源
是一个非常重要的基础组件,它的性能直接关系到数据持久层
的性能,尽管市面上有很多第三方数据源组件,比如阿里的druid
,Apache的DBCP
,c3p0
,不管是哪种数据源,最终都需要实现javax.sql.DataSource
接口
Mybatis框架自身也提供了数据源的实现
,分别是PooledDataSource
和UnpooledDataSource
学习Mybatis提供的数据源实现之前我们先看下javax.sql.DataSource接口,通过它来直观的了解作为一个数据源应该具有哪些功能?
DataSource接口
public interface DataSource extends CommonDataSource, Wrapper {
// 尝试通过数据源建立一个连接
Connection getConnection() throws SQLException;
// 重载的方法,传入用户名以及密码
Connection getConnection(String username, String password) throws SQLException;
}
通过DataSource接口我们可以知道,数据源本身就是就算是一个连接工厂,当你需要连接时,就问工厂要(调用getConnection方法)一个就行了
一般DataSource接口是由数据库驱动商实现,且基本上会有三种实现形式
基础实现:每次需要连接对象都是单纯的生产一个标准的新连接对象返回
连接池实现:生产的连接对象
自动加入
到连接池中以便复用
连接对象,该实现方式需要与一个中间层连接管理器
合作分布式事务方式实现:此种实现较为复杂,本文不会涉及
Mybaits中的数据源实现就是针对以上前两点实现的,UnpooledDataSource
对应基础实现方式
,而PooledDataSource
针对连接池方式的实现
,下面我们直接看源码
UnpooledDataSource
非池化数据源,即每次都是创建一个新的数据库连接对象返回
public class UnpooledDataSource implements DataSource {
private ClassLoader driverClassLoader;
// 驱动相关属性配置,在下面的工厂方法模式的setProperties方法中会将该属性赋值上(如果有驱动相关属性配置的化)
private Properties driverProperties;
// 已注册到驱动管理器的驱动集合,不同的数据库对应不同的驱动,比如mysql驱动,oracle驱动等...
private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<String, Driver>();
// 数据库驱动名称
private String driver;
// 数据库url
private String url;
// 用户名
private String username;
// 密码
private String password;
// 自动提交
private Boolean autoCommit;
// 默认隔离级别
private Integer defaultTransactionIsolationLevel;
static {
// 利用static块在UnpooledDataSource类初始化阶段,将已经注册到驱动管理器中的驱动考一份存入registeredDrivers集合中,以驱动的className为key保存
// 实际上基于SPI,只要classpath下有驱动的jar包,这里通过DriverManager.getDrivers()方法就可以直接获取到注册号的驱动了,因为这里会触发DriverManager类加载,并执行static块的loadInitialDrivers方法,根据ServiceLoader去加载数据库驱动jar中META-INF/services/下指定的文件(java.sql.Driver)
Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
Driver driver = drivers.nextElement();
registeredDrivers.put(driver.getClass().getName(), driver);
}
}
// 省略各种重载的构造函数...
// 省略各种属性的getter/setter方法...
@Override
public Connection getConnection() throws SQLException {
return doGetConnection(username, password);
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return doGetConnection(username, password);
}
// getConnection方法都会转到doGetConnection方法上
private Connection doGetConnection(String username, String password) throws SQLException {
Properties props = new Properties();
if (driverProperties != null) {
props.putAll(driverProperties);
}
if (username != null) {
props.setProperty("user", username);
}
if (password != null) {
props.setProperty("password", password);
}
// 最终调用的是下面的重载方法
return doGetConnection(props);
}
private Connection doGetConnection(Properties properties) throws SQLException {
// 1. 初始化驱动
initializeDriver();
// 2. 通过驱动管理器获取连接,注意如果1.中没有合适的驱动注册到驱动管理器中,这里的getConnection方法内,根据指定的url前缀(如:`jdbc:mysql:xxx`)就找不到合适的JDBC驱动,也就获取不到连接对象
Connection connection = DriverManager.getConnection(url, properties);
// 3. 配置连接对象
configureConnection(connection);
// 4. 返回连接对象
return connection;
}
// 只有不符合JDBC Driver SPI的驱动才可能会进入if内
private synchronized void initializeDriver() throws SQLException {
// 假设这里传入的driver为某种不符号SPI的驱动商驱动,第一次会进入if内
if (!registeredDrivers.containsKey(driver)) {
Class<?> driverType;
try {
if (driverClassLoader != null) {
// 如果driverClassLoader在配置文件中配置了,就进入这里
driverType = Class.forName(driver, true, driverClassLoader);
} else {
// 没有配置单独的driverClassLoader,则执行这里,加载driver驱动类
driverType = Resources.classForName(driver);
}
// DriverManager requires the driver to be loaded via the system ClassLoader.
// http://www.kfu.com/~nsayer/Java/dyn-jdbc.html
// 驱动类加载完成,开始实例化驱动
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);
}
}
}
private void configureConnection(Connection conn) throws SQLException {
// 配置连接对象,就是在已经获取到的连接上配置自动提交和默认事务的隔离级别
if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
conn.setAutoCommit(autoCommit);
}
if (defaultTransactionIsolationLevel != null) {
conn.setTransactionIsolation(defaultTransactionIsolationLevel);
}
}
// 静态内部类,Driver的静态代理类
private static class DriverProxy implements Driver {
private Driver driver;
DriverProxy(Driver d) {
this.driver = d;
}
@Override
public boolean acceptsURL(String u) throws SQLException {
return this.driver.acceptsURL(u);
}
@Override
public Connection connect(String u, Properties p) throws SQLException {
return this.driver.connect(u, p);
}
@Override
public int getMajorVersion() {
return this.driver.getMajorVersion();
}
@Override
public int getMinorVersion() {
return this.driver.getMinorVersion();
}
@Override
public DriverPropertyInfo[] getPropertyInfo(String u, Properties p) throws SQLException {
return this.driver.getPropertyInfo(u, p);
}
@Override
public boolean jdbcCompliant() {
return this.driver.jdbcCompliant();
}
// @Override only valid jdk7+
public Logger getParentLogger() {
return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
}
}
以上就是非池化数据源的全部源码,代码比较简单,对于熟悉JDBC编程的同学几乎没有难度,此外我们额外提一下关于该非池化数据源合的创建过程,以上源码我们省略了其构造函数,实际上Mybatis采用了工厂方法模式
来创建非池化以及池化数据源
工厂方法模式
Mybatis定义了一个DataSourceFactory接口来作为工厂方法模式中的工厂接口
public interface DataSourceFactory {
// 为DataSource设置相关属性
void setProperties(Properties props);
// 获取数据源对象
DataSource getDataSource();
}
那既然采用了工厂方法模式来创建不同的数据源实例,那么自然针对不同的产品(数据源)就会存在对应的工厂实现类,针对UnpooledDataSource
产品的工厂类实现就是UnpooledDataSourceFactory
类
利用工厂方法模式,Mybatis就可以直接面向工厂接口以及产品接口编程,而不用去管具体的工厂类和具体的产品类,与之带来的优点就是开闭原则
:对扩展开放
,对修改关闭
如果我们需要增加一种数据源(产品,比如增加一种第三方数据源),Mybatis只要再额外增加一种对应的工厂类就可以了
UnpooledDataSourceFactory
public class UnpooledDataSourceFactory implements DataSourceFactory {
// 以“driver”开头的属性
private static final String DRIVER_PROPERTY_PREFIX = "driver.";
private static final int DRIVER_PROPERTY_PREFIX_LENGTH = DRIVER_PROPERTY_PREFIX.length();
protected DataSource dataSource;
// 利用工厂的构造函数直接new一个数据源对象出来
public UnpooledDataSourceFactory() {
this.dataSource = new UnpooledDataSource();
}
@Override
public void setProperties(Properties properties) {
// 抽出属性配置中的驱动相关配置,并保存到driverProperties中
Properties driverProperties = new Properties();
MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
for (Object key : properties.keySet()) {
String propertyName = (String) key;
if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
// 获取驱动相关属性名(去除前缀driver.的),保存
String value = properties.getProperty(propertyName);
driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
} else if (metaDataSource.hasSetter(propertyName)) {
String value = (String) properties.get(propertyName);
Object convertedValue = convertValue(metaDataSource, propertyName, value);
// dataSource相关的配置属性,直接set进去
metaDataSource.setValue(propertyName, convertedValue);
} else {
throw new DataSourceException("Unknown DataSource property: " + propertyName);
}
}
if (driverProperties.size() > 0) {
// 最后,如果有驱动相关的属性配置的化,将其设置到dataSource对象的driverProperties属性上去
metaDataSource.setValue("driverProperties", driverProperties);
}
}
@Override
public DataSource getDataSource() {
return dataSource;
}
}
至此我们解析了整个Mybatis提供的非池化数据源的创建过程,可见底层还是调用了Java JDBC相关的代码的
总结
- 调用非池化数据源工厂类
UnpooledDataSourceFactory
的构造函数,创建工厂对象
- 工厂对象的
构造函数中
调用非池化数据源类UnpooledDataSource
的构造函数,创建非池化数据源对象
- 调用非池化数据源
工厂对象
的setProperties(...)
方法,根据入参Properties,设置非池化数据源对象的基本属性(如url
,username
,password
等)以及赋值driverProperties
属性 - 最后就可以调用非池化数据源工厂对象的
getDataSource
方法获取数据源对象
实例啦
下文会分析Mybatis提供的另一种数据源实现,即池化数据源PooledDataSource
的源码
最后附上Mybatis针对数据源的配置文件写法,帮助同学回忆一下哈
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 对事务的管理和连接池的配置 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<!-- oracle驱动 -->
<property name="driver" value="oracle.jdbc.driver.OracleDriver" />
<property name="url" value="jdbc:oracle:thin:@localhost:1521:orcl" />
<property name="username" value="ibatis" />
<property name="password" value="ibatis" />
</dataSource>
</environment>
</environments>
<!-- mapping 文件路径配置 -->
<mappers>
<mapper resource="com/yu/res/UserMapper.xml" />
</mappers>
</configuration>
如果是mysql驱动的化上面的value值就是com.mysql.jdbc.Driver
,另外url属性值就是类似这样的jdbc:mysql://xxx:3306/wilson?serverTimezone=UTC
,另外一点,以上配置没有单独配置driverClassLoader驱动类加载器
最后关于JDBC的Driver驱动类加载可以看看xwiz大神的这篇文章