简述

在数据持久层中,数据源是一个非常重要的组件,其性能直接关系到整个数据持久层的性能。在实践中比较常见的第三方数据源组件有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. oracle学习笔记03

    一:表空间 /* 创建表空间:逻辑单位,通常我们新建一个项目,就会去创建表空间,在表空间中创建用户,用户去创建表. 语法:create tablespace 表空间名字 datafile '文件的路径 ...

  2. PLSQL PL/SQL Developer Oracle 使用技巧 常用设置 卡顿问题 病毒防范( 附带:配置文件)

    相关工具版本: PL/SQL Developer: 9.0.4.1644 Oracle : Oracle Database 10g Enterprise Edition Release 10.2.0. ...

  3. Install CUDA 6.0 on Ubuntu 14.04 LTS

    Ubuntu 14.04 LTS is out, loads of new features have been added. Here are some procedures I followed ...

  4. luogu1156垃圾陷阱题解--背包DP

    题目链接 https://www.luogu.org/problemnew/show/P1156 方法1 分析 将已经爬的高度看作背包容积,最大剩余血量看作价值,\(f[i][j]\)表示吃完第\(i ...

  5. Java基础第一天--继承、修饰符

    继承 继承的概述: 继承是面向对象三大特征之一.可以使得子类具有父类的属性和方法,还可以在子类中重新定义,追加属性和方法. //创建父类 public class Fu{ public void sh ...

  6. js重点——作用域——作用域分类(三)

    一.作用域可以分为全局作用域,局部作用域(函数作用域)和块级作用域. 1.全局作用域 代码在程序中的任何位置都能被访问到,window对象的内置属性都拥有全局作用域. <script> v ...

  7. datagrid如何获取选中行的索引

    //datagrid获取选中行 var row =baseSelectgrid.datagrid('getSelected'); // 获取被选中行的索引 index var index=baseSe ...

  8. # 机器学习算法总结-第四天(SKlearn/数据处理and特征工程)

    总结: 量纲化(归一化,标准化) 缺失值处理(补0.均值.中值.众数.自定义) 编码/哑变量:忽略数字中自带数学性质(文字->数值类型) 连续特征离散化(二值化/分箱处理)

  9. 如何成为优秀的技术Leader

    技术主管,又叫技术经理,英文一般是 Tech Leader ,简称 TL.随着工作经验的不断积累,能力的不断提升,每个人都有机会成为 Team Leader. 然而在机会到来前,我们必须提前做好准备, ...

  10. ASE19团队项目alpha阶段model组 scrum5 记录

    本次会议于11月7日,19时整在微软北京西二号楼sky garden召开,持续12分钟. 与会人员:Jiyan He, Kun Yan, Lei Chai, Linfeng Qi, Xueqing W ...