Mybatis是如何通过SqlSessionFactory得到SqlSession的
本篇文章是通过看视频学习总结的内容, 如有错误的地方请谅解,并联系博主及时修改,谢谢您的阅读.
源码地址: mybatis 中文注释版
《Mybatis(二) - Mybatis是如何创建出SqlSessionFactory的》
《Mybatis(一) - Mybatis 最原始是使用方式》
从 《Mybatis(一) - Mybatis 最原始是使用方式》 中 Junit 测试方法开始,通过 SqlSession session = this.sqlSessionFactory.openSession();
SqlSessionFactory
得到 SqlSessoin
,那么在本文中将通过源码的角度来鉴赏Mybatis为查询创建会话。
- 进入方法
sqlSessionFactory.openSession()
中,会发现SqlSession
居然只是个接口,Ctrl + alt + B
发现有两个实现类,一个是DefaultSqlSessionFactory
,另一个是SessionManager
,很明显,在《Mybatis(二)》文章结尾处,Mybatis 此时只是用了DefaultSqlSessionFactory
,还没有涉及到其他的类,那么我们只需要跟进DefaultSqlSessionFactory
即可,从代码中可以看出5步就完成了session
的创建,那么再细细看一下5步具体干了什么。
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 1 得到配置的数据源信息
final Environment environment = configuration.getEnvironment();
// 2 获取事务工厂
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 3 创建事务
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 4 根据事务工厂和默认的执行器类型,创建执行器 >>
final Executor executor = configuration.newExecutor(tx, execType);
// 5 创建默认的sqlSession,创建sqlSession主要是通过初始化的时候加载的全局配置和执行器
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
一、configuration.getEnvironment();
- 1.1 这一步很简单,只是从初始化完毕后的
Configuration
对象中获取环境,提供给第二步创建事务的时候使用,我都忘了环境变量是怎么读取的了,我得去看看,找到org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration()
方法,原来是数据源啊。
private void parseConfiguration(XNode root) {
try {
....
// 数据源的创建
environmentsElement(root.evalNode("environments"));
.....
} catch (Exception e) {
}
}
- 1.2 实在想不起这玩意儿是干啥的,就继续进入
mybatis-config.xml
查看environments
节点
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/><!-- 单独使用时配置成MANAGED没有事务 -->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
二、getTransactionFactoryFromEnvironment(environment);
- 2.1 通过用户设置的数据源信息创建事务工厂,这里事务工厂先放一放,这里不做详细分析
private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
if (environment == null || environment.getTransactionFactory() == null) {
return new ManagedTransactionFactory();
}
return environment.getTransactionFactory();
}
三、transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit)
在聊这个方法的前提,是先搞明白方法三个参数的意义
environment.getDataSource(): dataSource数据源,配置在 mybatis-config.xml中 environments 节点中
level: 追溯到该方法的调用处,是一个null值,通过字面意思可以理解为创建会话的等级
autoCommit: PrepareStatment执行完毕后会有提交操作,这里的autoCommit就是设置 ps.commit(); 的
- 3.1
TransactionFactory
是一个接口,实现类有两个(JdbcTransactionFactory
和ManagedTransactionFactory
),在第二步
中 如果数据源或者是数据源事务工厂不存在的话,那么肯定是创建的ManagedTransactionFactory()
, 但是通过前面的分析结果,其实environment == null || environment.getTransactionFactory() == null
不满足情况,那么肯定是走else
的逻辑,最终直接拿到了数据源中的事务工厂
四、configuration.newExecutor(tx, execType)
- 4.1 这一步就很明显了,开始通过配置文件中的执行器类型和事务,去创建执行器
那么来看看执行器有哪些
public enum ExecutorType {
// SIMPLE: 简单执行器 (默认执行器),可以通过方法configuration.getDefaultExecutorType() 找到,定义在 Configuration 对象中 protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
// REUSE: 重用执行器
// BATCH: 批量执行器
SIMPLE, REUSE, BATCH
}
- 4.2 进入
newExecutor()
, 在这个方法里面,不仅创建了SimpleExecutor
对象,如果开启二级缓存的情况下还对Excutor
做了缓存的装饰,最后还对插件进行了植入,这里先留一个悬念,为什么植入插件会是在执行器中完成?
/**
* 创建sql执行器
* @param transaction 事务
* @param executorType 执行器类型: BATCH REUSE SIMPLE 三种,默认是SIMPLE类型
* @return
*/
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
}
// 默认会走这里的逻辑
else {
// 默认 SimpleExecutor
executor = new SimpleExecutor(this, transaction);
}
// 二级缓存开关,settings 中的 cacheEnabled 默认是 true
if (cacheEnabled) {
// 这里使用了装饰器模式,来增强执行器对象
executor = new CachingExecutor(executor);
}
// 植入插件的逻辑,至此,四大对象已经全部拦截完毕
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
五、return new DefaultSqlSession(configuration, executor, autoCommit)
- 5.1
new
出一个默认的SqlSession
对象,然后返回,此时的SqlSession
对象中已经包含了全局配置对象、执行器、是否自动提交
这几个对象
六、第四点中遗留悬念
- 为什么植入插件是在执行器中去完成的?
通过对 Mybatis官网 插件的描述,插件只会去拦截
Executor 、ParameterHandler、ResultSetHandler、StatementHandler
四大对象。纵观全局这四大对象都处于一个会话中的,那么毫无疑问,插件初始化肯定会存在会话创建过程中完成。但是插件又是一个全局的,存在与Configuration
对象中。因此初始化执行器的时候,将插件初始化到全局Configuration
中
总结:
- 从三篇文章中,首先是初始化配置文件,得到Configuration 对象。
Configuration
对象在初始化的时候注册了类的别名、数据源配置、事务工厂配置,插件的解析,sqlXML
的解析。Configuration
初始化同时也完成了对mapper.xml
文件的解析,得到了Mapper.java
+ 方法名(statementId
) 对应sql 的映射关系(com.example.mapper.UserMapper.getUserById --> select * from t_user where id = ?)
- 得到
statementId
并且绑定了 sql 执行后的返回结果类型- 以上所有数据全部准备齐全后,执行调用
jdbc
的执行方法,则可以获取到返回结果,封装成 第四步得到的pojo
类型