简述

在数据持久层中,数据源是一个非常重要的组件,其性能直接关系到整个数据持久层的性能。在实践中比较常见的第三方数据源组件有Apache Common DBCP、C3P0、Proxool等,MyBatis不仅可以集成第三方数据源组件,还提供了自己的数据源实现。

常见的数据源组件都实现了javax.sql.DataSource接口,MyBatis自身实现的数据源实现也不例外。MyBatis提供了两个javax.sql.DataSource接口实现,分别是PooledDataSource和UnpooledDataSource。Mybatis使用不同的DataSourceFactory接口实现创建不同类型的DataSource,

DataSource数据源分类

Mybatis源码在org.apache.ibatis.datasource包中:

mybatis配置文件中关于数据库的配置:

<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>

这里重点关注一下节点中的type属性,有三种取值,分别是:

UNPOOLED    不使用任何数据库连接池管理数据库连接

POOLED      使用Mybatis默认的数据库连接池管理数据库连接

JNDI        使用JNDI实现的数据源

MyBatis是通过工厂模式来创建数据源DataSource对象的,MyBatis定义了抽象的工厂接口:org.apache.ibatis.datasource.DataSourceFactory,通过其getDataSource()方法返回数据源DataSource

/**
* 数据源工厂接口
*
* @author kaifeng
* @author Clinton Begin
*/
public interface DataSourceFactory { /**
* 设置数据源相关属性
*
* @param props 数据源属性
*/
void setProperties( Properties props ); /**
* 获取数据源对象
*/
DataSource getDataSource(); }

三种不同类型的type,分别有对应的dataSource工厂:

POOLED        PooledDataSourceFactory
UNPOOLED UnpooledDataSourceFactory
JNDI JndiDataSourceFactory

UnpooledDataSourceFactory 和 JndiDataSourceFactory 实现了DataSourceFactory接口

PooledDataSourceFactory 继承了 UnpooledDataSourceFactory

DataSource实例化过程

1、解析配置文件并装配Cofiguration对象


Mybatis 解析配置文件并装配Cofiguration对象,同时Configuration中的属性

protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();

被实例化,并且在Configuration的无参构造函数中,向 TypeAliasRegistry的属性

private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();

中注册了许多常用的类,TypeAliasRegistry的构造函数中也注册了基本的java类型

2、根据type=POOLED属性查找工厂类


根据配置文件type=POOLED属性,从TypeAliasRegistry中查找对应的工厂类 PooledDataSourceFactory

3、实例化DataSource


由于PooledDataSourceFactory继承自UnpooledDataSourceFactory、所以UnpooledDataSourceFactory先被实例化、

UnpooledDataSourceFactory无参构造方法中实例化了其DataSource为UnpooledDataSource。

/**
* @author kaifeng
* @author Clinton Begin
*/
public class UnpooledDataSourceFactory implements DataSourceFactory { private static final String DRIVER_PROPERTY_PREFIX = "driver.";
private static final int DRIVER_PROPERTY_PREFIX_LENGTH = DRIVER_PROPERTY_PREFIX.length(); protected DataSource dataSource; public UnpooledDataSourceFactory() {
this.dataSource = new UnpooledDataSource();
} }

接下来实例化 PooledDataSourceFactory 其无参构造方法体将父类UnpooledDataSourceFactory持有的DataSource实例化为PooledDataSource

/**
* 将父类UnpooledDataSourceFactory的dataSource实例化为PooledDataSource
*
* @author kaifeng
* @author Clinton Begin
*/
public class PooledDataSourceFactory extends UnpooledDataSourceFactory { public PooledDataSourceFactory() {
this.dataSource = new PooledDataSource();
} }

PooledDataSource实例化时初始化了一些关于数据库连接池的配置信息

PooledDataSource的无参构造方法中将其持有的UnpooledDataSource实例化。

/**
* Mybatis默认数据库连接池
*
* @author kaifeng
* @author Clinton Begin
*/
public class PooledDataSource implements DataSource { private static final Log log = LogFactory.getLog(PooledDataSource.class); private final PoolState state = new PoolState(this); private final UnpooledDataSource dataSource; /**
* 最大活动连接数(默认为10)
*/
protected int poolMaximumActiveConnections = 10; /**
* 最大空闲连接数(默认为5)
*/
protected int poolMaximumIdleConnections = 5; /**
* 最大可回收时间,即当达到最大活动链接数时,此时如果有程序获取连接,则检查最先使用的连接,看其是否超出了该时间,如果超出了该时间,则可以回收该连接。(默认20s)
*/
protected int poolMaximumCheckoutTime = 20000; /**
* 没有连接时,尝试获取连接以及打印日志的时间间隔(默认20s)
*/
protected int poolTimeToWait = 20000; /**
* 这是一个关于无效连接容忍度的底层设置, 作用于每一个尝试从缓存池获取连接的线程.
* 如果这个线程获取到的是一个无效的连接,那么这个数据源允许这个线程尝试重新获取一个新的连接,
* 但是这个重新尝试的次数不应该超过 poolMaximumIdleConnections 与 poolMaximumLocalBadConnectionTolerance 之和。
* 默认值:3 (新增于 3.4.5)
*/
protected int poolMaximumLocalBadConnectionTolerance = 3; /**
* 检查数据源是否可访问的语句,默认为"NO PING QUERY SET",即没有,使用会导致抛异常
*/
protected String poolPingQuery = "NO PING QUERY SET"; /**
* 是否开启ping检测,(默认:false)
*/
protected boolean poolPingEnabled; /**
* 设置ping检测时间间隔,通常用于检测超时连接(默认为0,即当开启检测后每次从连接词中获取连接以及放回连接池都需要检测)
*/
protected int poolPingConnectionsNotUsedFor; private int expectedConnectionTypeCode; public PooledDataSource() {
dataSource = new UnpooledDataSource();
} }

UnpooledDataSource中关于数据库连接的属性值在实例化DataSourceFactory之后读取properties值设置到对应属性上。

/**
* 不使用连接池的数据源
*
* @author kaifeng
* @author Clinton Begin
* @author Eduardo Macarron
*/
public class UnpooledDataSource implements DataSource {
/**
* 数据源驱动类加载器
*/
private ClassLoader driverClassLoader;
/**
* 驱动连接属性
*/
private Properties driverProperties; /**
* 已注册的驱动集合
*/
private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<String, Driver>(); /**
* 当前使用的驱动
*/
private String driver;
/**
* 数据源地址
*/
private String url;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 是否自动提交事务
*/
private Boolean autoCommit; /**
* 默认事务隔离级别
*/
private Integer defaultTransactionIsolationLevel; // 静态代码块,当类加载的时候,就从DriverManager中获取所有的驱动信息,放到当前维护的Map中
static {
Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
Driver driver = drivers.nextElement();
registeredDrivers.put(driver.getClass().getName(), driver);
}
} public UnpooledDataSource() {
} }

如何从配置文件初始化DataSource

由上文得知Mybatis初始化过程是解析Mybatis配置文件并装配Configuration对象,从Mybatis配置文件中知道Mybatis数据库连接信息的配置是在environments标签中配置的:

<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>

所以我们可以从XMLConfigBuilder中的parse方法入手

private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}

我们看其中的关键代码

dataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();

根据配置文件中dataSource标签中内容实例化DataSourceFactory,

通过DataSourceFactory获取DataSource

那么如何根据dataSource标签内容实例化DataSourceFactory的呢,我们看一下 dataSourceElement 方法:

private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
//获取数据库连接池类型: POOLED-使用Mybatis自带数据库连接池。UNPOOL-不使用数据库连接池
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}

我们看关键代码 resolveClass(type)

经过一系列的方法调用、最终返回结果是:TypeAliasRegistry。

protected Class<?> resolveClass(String alias) {
if (alias == null) {
return null;
}
try {
return resolveAlias(alias);
} catch (Exception e) {
throw new BuilderException("Error resolving class. Cause: " + e, e);
}
} protected Class<?> resolveAlias(String alias) {
return typeAliasRegistry.resolveAlias(alias);
}

上文中已经说明,在装配Configuration时,其无参构造函数已经向typeAliasRegistry注册了常用类,至此我们对数据源的初始化也就明了了。

public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class); typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class); typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class); typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class); typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class); typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class); typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class); languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}

Connection对象创建时机

当我们需要执行SQL的时候,mybatis会调用DataSource对象创建java.sql.Connection对象,直到执行SQL时,Connection对象才会被创建,下面我们通过一个事例验证一下。

 @Test
public void testGetSqlSession() throws Exception {
//解析mybatis.xml,获取SqlSession对象
1、SqlSession sqlSession = MybatisUtil.getSqlSession();
2、AuthorMapper authorMapper = sqlSession.getMapper(AuthorMapper.class);
3、System.err.println("即将创建Connection对象,执行SQL语句");
4、authorMapper.getAllAuthors();
} MybatisUtil中的方法 /**
* @author kaifeng
*/
public class MybatisUtil {
private static SqlSessionFactory sqlSessionFactory; /**
* 解析mybatis.xml文件创建SqlSessionFactory对象
*
* @return SqlSessionFactory instance
*/
public static SqlSessionFactory getSqlSessionFactory() {
String mybatisConfigPath = "config/mybatis.xml";
try {
InputStream inputStream = Resources.getResourceAsStream(mybatisConfigPath);
if (sqlSessionFactory == null) {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
} catch (IOException e) {
e.printStackTrace();
}
return sqlSessionFactory;
} /**
* 通过SqlSessionFactory打开SqlSession与数据库的会话
*
* @return SqlSession 返回SqlSession对象
*/
public static SqlSession getSqlSession() {
return MybatisUtil.getSqlSessionFactory().openSession();
} }

执行上边的单元测试代码,这里给出一部分关键的日志

2018-08-04 15:01:26 [org.apache.ibatis.parsing.GenericTokenParser.parse(GenericTokenParser.java:121)]-[DEBUG] [GenericTokenParser]-[parse]-待解析文本:DELETE FROM author
WHERE id = #{id},解析结果:DELETE FROM author
WHERE id = ?
2018-08-04 15:01:26 [org.apache.ibatis.parsing.GenericTokenParser.parse(GenericTokenParser.java:121)]-[DEBUG] [GenericTokenParser]-[parse]-待解析文本:UPDATE author
SET username = #{username},解析结果:UPDATE author
SET username = ?
2018-08-04 15:01:26 [org.apache.ibatis.parsing.GenericTokenParser.parse(GenericTokenParser.java:121)]-[DEBUG] [GenericTokenParser]-[parse]-待解析文本:SELECT
t1.id post_id,
t1.created_on createdTime,
t1.section,
t1.subject,
t1.draft,
t1.body,
t2.id post_comment_id,
t2.name post_comment_name,
t2.comment_text post_comment_text
FROM post t1 LEFT JOIN post_comment t2 ON t1.id = t2.post_id
WHERE t1.id = #{id},解析结果:SELECT
t1.id post_id,
t1.created_on createdTime,
t1.section,
t1.subject,
t1.draft,
t1.body,
t2.id post_comment_id,
t2.name post_comment_name,
t2.comment_text post_comment_text
FROM post t1 LEFT JOIN post_comment t2 ON t1.id = t2.post_id
WHERE t1.id = ?
即将创建Connection对象,执行SQL语句
2018-08-04 15:01:26 [org.apache.ibatis.transaction.jdbc.JdbcTransaction.openConnection(JdbcTransaction.java:137)]-[DEBUG] Opening JDBC Connection
2018-08-04 15:01:26 [org.apache.ibatis.datasource.pooled.PooledDataSource.popConnection(PooledDataSource.java:467)]-[DEBUG] Created connection 854487022.
2018-08-04 15:01:26 [org.apache.ibatis.transaction.jdbc.JdbcTransaction.setDesiredAutoCommit(JdbcTransaction.java:101)]-[DEBUG] Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@32ee6fee]
2018-08-04 15:01:26 [org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:159)]-[DEBUG] ==> Preparing: SELECT t.id, t.username, t.password, t.email, t.bio, t.favourite_section favouriteSection FROM author t
2018-08-04 15:01:26 [org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:159)]-[DEBUG] ==> Parameters:
Disconnected from the target VM, address: '127.0.0.1:50293', transport: 'socket'
2018-08-04 15:01:26 [org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:159)]-[DEBUG] <== Total: 1

从日志中我们可以看出直到执行第4句代码时,才会触发openConnection()方法创建java.sql.Connection对象

protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Opening JDBC Connection");
}
connection = dataSource.getConnection();
if (level != null) {
connection.setTransactionIsolation(level.getLevel());
}
setDesiredAutoCommit(autoCommmit);
}

Mybatis源码学习之DataSource(七)_1的更多相关文章

  1. Mybatis源码学习之DataSource(七)_2

    接上节数据源,本节我们将继续学习未完成的部分,包括无连接池情况下的分析.为什么使用连接池.及mybatis连接池的具体管理原理 不使用连接池的UnpooledDataSource 当 的type属性为 ...

  2. mybatis源码学习(一) 原生mybatis源码学习

    最近这一周,主要在学习mybatis相关的源码,所以记录一下吧,算是一点学习心得 个人觉得,mybatis的源码,大致可以分为两部分,一是原生的mybatis,二是和spring整合之后的mybati ...

  3. mybatis源码学习:一级缓存和二级缓存分析

    目录 零.一级缓存和二级缓存的流程 一级缓存总结 二级缓存总结 一.缓存接口Cache及其实现类 二.cache标签解析源码 三.CacheKey缓存项的key 四.二级缓存TransactionCa ...

  4. mybatis源码学习:基于动态代理实现查询全过程

    前文传送门: mybatis源码学习:从SqlSessionFactory到代理对象的生成 mybatis源码学习:一级缓存和二级缓存分析 下面这条语句,将会调用代理对象的方法,并执行查询过程,我们一 ...

  5. mybatis源码学习:插件定义+执行流程责任链

    目录 一.自定义插件流程 二.测试插件 三.源码分析 1.inteceptor在Configuration中的注册 2.基于责任链的设计模式 3.基于动态代理的plugin 4.拦截方法的interc ...

  6. Mybatis源码学习第六天(核心流程分析)之Executor分析

    今Executor这个类,Mybatis虽然表面是SqlSession做的增删改查,其实底层统一调用的是Executor这个接口 在这里贴一下Mybatis查询体系结构图 Executor组件分析 E ...

  7. Mybatis源码学习第八天(总结)

    源码学习到这里就要结束了; 来总结一下吧 Mybatis的总体架构 这次源码学习我们,学习了重点的模块,在这里我想说一句,源码的学习不是要所有的都学,一行一行的去学,这是错误的,我们只需要学习核心,专 ...

  8. mybatis源码学习(三)-一级缓存二级缓存

    本文主要是个人学习mybatis缓存的学习笔记,主要有以下几个知识点 1.一级缓存配置信息 2.一级缓存源码学习笔记 3.二级缓存配置信息 4.二级缓存源码 5.一级缓存.二级缓存总结 1.一级缓存配 ...

  9. Mybatis源码学习之整体架构(一)

    简述 关于ORM的定义,我们引用了一下百度百科给出的定义,总体来说ORM就是提供给开发人员API,方便操作关系型数据库的,封装了对数据库操作的过程,同时提供对象与数据之间的映射功能,解放了开发人员对访 ...

随机推荐

  1. cas sso 整合记录

    首先说明下,我使用的cas-server版本是4.2.1 整合过程中遇到的问题及解决方式如下 1.因为使用https的话证书是个麻烦事,所以启用http 修改cas-server-webapp下的ca ...

  2. Linux环境下安装Nginx及其使用

    https://www.jb51.net/article/136161.htm 一.查看CentOS的版本 ? 1 cat /etc/redhat-release 二.添加资源库 在 CentOS 系 ...

  3. div可以同时设置背景图片和背景颜色吗?

    前言 当然可以同时设置 当图片背景色不透明时 情况一:当图片的长.宽 >= div的长.宽时 我们最终看到div背景是图片,之所以说是最终看到,是因为在页面加载时,我们先看到的div背景是颜色, ...

  4. Python 数字(函数)

    Python支持4种不同数值类型: 整型(Int) - 通常被称为是整型或整数,是正或负整数,不带小数点. 长整型(long integers) - 无限大小的整数,整数最后是一个大写或小写的L. 浮 ...

  5. 一个下午整理的Web前端常见的英文缩写

    PV (Page View)页面浏览量 FED(Front-End Development)前端开发 F2E(Front-End Engineer)前端工程师 WWW(World Wide Web)万 ...

  6. java集合之hashMap,初始长度,高并发死锁,java8 hashMap做的性能提升

    众所周知,HashMap是一个用于存储Key-Value键值对的集合,每一个键值对也叫做Entry.这些个键值对(Entry)分散存储在一个数组当中,这个数组就是HashMap的主干. HashMap ...

  7. vue-element-admin实现模板打印

    一.简介 模板打印也叫”套打“,是业务系统和后台管理系统中的常用功能,B/S系统中实现”套打“比较繁琐,所以很多的B/S系统中的打印功能一直使用的是浏览器打印,很少实现模板打印.本篇将介绍在Vue E ...

  8. springboot2集成activiti出错

    报一个反射错误 java.lang.ArrayStoreException: sun.reflect.annotation.TypeNotPresentExceptionProxy 解决方案:http ...

  9. 通过shell发送邮件

    安装mailx CentOS 7自带有mailx软件包, 有/usr/bin/mail命令, 配置文件为/etc/mail.rc. 如果没有软件包, 可以安装 CentOS/Fedora yum in ...

  10. MODI的OCR接口

    MODI的OCR接口 MODI的OCR接口 MODI的OCR接口