今Executor这个类,Mybatis虽然表面是SqlSession做的增删改查,其实底层统一调用的是Executor这个接口
在这里贴一下Mybatis查询体系结构图
Executor组件分析
Executor是Mybatis的核心组件之一,定义了数据库操作最基本的方法,SqlSession的功能都是基于它实现的;
在分析这个之前先来说一下 大家不用想也知道 设计模式白,之前都是这么开始讲的,没错在这里我说一下模板模式
模板模式:一个抽象类公开定义了执行他方法的方式/模板,他的子类可以按需要重写方法的实现,但调用将以抽象类中定义的方法执行,定义一个操作中算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定实现;
模板模式大家应该也都熟悉,举一些例子吧:就HttpClient的restTemplate,redisTemplate,等都是模板模式的标准样式;
模板模式的应用场景:
遇到由一系列步骤构成的过程需要执行,这个过程从高层次上看是相同的,但是有些步骤的实现可能不同,这个时候就需要考虑用模板模式了.
接下来我们说一下这个Executor吧;
其中就采用了模板模式,BaseExecutor就是模板模式中的抽象层,他实现了Executor接口的大部分方法,主要提供了缓存管理和事物管理的能力,其他子类需要实现的抽象方法为doUpate,doQuery等方法;
然后说一下Mybatis一次完整的执行流程吧
SqlSession执行
统一归到Executor的query中
贴一下executor.query的代码吧
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
开始获取Sql语句 从MappedStatement中获取,因为在初始化的时候是存储在MappedStatement中的,这里就不多讲了,想要了解的可以看我之前写的,也可以自行百度
通过已有的4种信息开始创建缓存的CacheKey,至于具体的可以看我之前分析Cache基础支撑模块的分析;
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) { //对延迟加载的数据进行处理
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { //如果当前Sql的一级缓存配置为STATEMENT,查询完立即清空一级缓存
// issue #482
clearLocalCache();
}
}
return list;
}
非嵌套查询,并且FlushCache配置为true则清空一级缓存
根据CacheKey查询一级缓存,说道这里有人就会问,MyBatis不是先查二级缓存吗?不要着急,Mybatis的二级缓存不是在这里写的,等一会在来说,如果只是单纯分析BaseExecutor那么确实是直接查询一级缓存的
如果命中一级缓存 那么返回,如果没有命中那么从数据库中加载数据(有三种方法:Simple|Reuse|Batch),放入一级缓存中.
BaseExecutor定义了这样的一个查询骨架,能修改的或者说给子类重写实现的就只有关于数据库操作这一块了
Executor的三个实现类解读
SimpleExecutor:默认配置,使用PrepareStatement对象访问数据库,每次访问都要创建新的PrepareStatement对象;
ReuseExecutor:使用预编译PrepareStatement对象访问数据库,访问时,会重用缓存中的statement对象;
BatchExecutor:实现批量执行多条SQL语句的能力;
先来SimpleExecutor
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
//获取全局Configuration
Configuration configuration = ms.getConfiguration();
// 创建StatementHandler对象
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// StatementHandler对象创建stmt,并用prepareStatement对占位符进行处理
stmt = prepareStatement(handler, ms.getStatementLog());
// 通过statementHandler对象调用ResultSetHandler将结果集转化为指定对象返回
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 在这里获取的Connetion
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
// 在这里返回的就是调用动态代理增强后的Connection
// 要了解日志模块关于参数语句等打印的请看之前的文章mybatis源码学习第一天 (logging模块),
// 在这里优雅的嵌入到代码中采用调用链的方式返回之后的打印增强类
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
简单说一下这个日志打印的调用链把 ConnectionLogger -> PrepareStatementLogger -> ResultSetLogger
之后再说StatementHandler,其实也是模板模式,抽象类构建骨架,三个子类实现,大家也可以先去看看,没啥难的
然后是ReuseExecutor
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
}
看着一样是吧,确实没啥区别,具体的区别在prepareStatement方法中
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql(); //获取Sql语句
if (hasStatementFor(sql)) { // 根据Sql语句检查是否缓存了对应的Statement
stmt = getStatement(sql); //获取缓存的Statement
applyTransactionTimeout(stmt); //设置新的超时时间
} else { // 缓存中没有statement创建Statement,创建statement过程和SimpleExecutor类似
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
putStatement(sql, stmt); // 放入缓存中
}
// 使用PrepareStatement处理占位符
handler.parameterize(stmt);
return stmt;
}
不用想也知道缓存肯定是Map
private final Map<String, Statement> statementMap = new HashMap<>();
没错这个就是缓存的Map 在41行搁着呢,好了不逗了......接着说别的
二级缓存就是通过装饰器把CachingExecutor包装BaseExecutor,也就是二级缓存如果开启的话,就会包装一层CachingExecutor,所以说会先走二级缓存再走一级缓存,如果二级缓存没有开启的话,那么就直接走一级缓存
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 从MapperStatement中获取二级缓存
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
// 二级缓存为空,才会调用BaseExecutor.query
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
装饰器模式之前在Cache模块中说过,不了解请百度一下,或看我之前写的Cache模块
Executor的三个执行器
通过对 SimpleExecutor doQuery方法的解读发现,Executor是指挥官,他在调度三个执行器工作;
StatementHandler:他的作用是使用数据库的Statement或PrepareStatement执行操作,启承上启下的作用;
ParameterHandler:对预编译的 Sql语句进行参数设置,SQL语句中的占位符 ? 都对应BoundSql.parameterMappings集合中的一个元素,在该对象中记录了对应的参数名称以及该参数的相关属性
ResultSetHandler:对数据库返回的结果集(ResultSet)进行封装,返回用户指定的实例类型;
在这里就像 SqlSession 说我不干活 Executor 你去干,我也不干 StatementHandler你去干
就像是 售前 ->> 项目经理 ->> 开发人员
在这里的三个执行器分别对应不同时段的处理
StatementHandler -> parameterHandler -> resultSetHandler
今天先写到这里吧,有点晚了,明天吧剩余的一点写一下!抱歉
作者:彼岸舞
时间:2020\03\22
内容关于:Mybatis
本文部分来源于网络,只做技术分享,一概不负任何责任
Mybatis源码学习第六天(核心流程分析)之Executor分析的更多相关文章
-
Mybatis源码学习第六天(核心流程分析)之Executor分析(补充)
补充上一章没有讲解的三个Executor执行器; 还是贴一下之前的代码吧;我发现其实有些分析注释还是写在代码里面比较好,方便大家理解,之前是我的疏忽,不好意思 @Override public < ...
-
mybatis源码学习:一级缓存和二级缓存分析
目录 零.一级缓存和二级缓存的流程 一级缓存总结 二级缓存总结 一.缓存接口Cache及其实现类 二.cache标签解析源码 三.CacheKey缓存项的key 四.二级缓存TransactionCa ...
-
mybatis源码学习:插件定义+执行流程责任链
目录 一.自定义插件流程 二.测试插件 三.源码分析 1.inteceptor在Configuration中的注册 2.基于责任链的设计模式 3.基于动态代理的plugin 4.拦截方法的interc ...
-
mybatis源码学习:基于动态代理实现查询全过程
前文传送门: mybatis源码学习:从SqlSessionFactory到代理对象的生成 mybatis源码学习:一级缓存和二级缓存分析 下面这条语句,将会调用代理对象的方法,并执行查询过程,我们一 ...
-
mybatis源码学习(一) 原生mybatis源码学习
最近这一周,主要在学习mybatis相关的源码,所以记录一下吧,算是一点学习心得 个人觉得,mybatis的源码,大致可以分为两部分,一是原生的mybatis,二是和spring整合之后的mybati ...
-
Mybatis源码学习第八天(总结)
源码学习到这里就要结束了; 来总结一下吧 Mybatis的总体架构 这次源码学习我们,学习了重点的模块,在这里我想说一句,源码的学习不是要所有的都学,一行一行的去学,这是错误的,我们只需要学习核心,专 ...
-
Mybatis源码学习之整体架构(一)
简述 关于ORM的定义,我们引用了一下百度百科给出的定义,总体来说ORM就是提供给开发人员API,方便操作关系型数据库的,封装了对数据库操作的过程,同时提供对象与数据之间的映射功能,解放了开发人员对访 ...
-
redis源码学习之工作流程初探
目录 背景 环境准备 下载redis源码 下载Visual Studio Visual Studio打开redis源码 启动过程分析 调用关系图 事件循环分析 工作模型 代码分析 动画演示 网络模块 ...
-
Spring mybatis源码学习指引目录
前言: 分析了很多方面的mybatis的源码以及与spring结合的源码,但是难免出现错综的现象,为了使源码陶冶更为有序化.清晰化,特作此随笔归纳下分析过的内容.博主也为mybatis官方提供过pul ...
随机推荐
-
PHP中获取当前页面的完整URL
//获取域名或主机地址 echo $_SERVER['HTTP_HOST']."<br>"; #localhost//获取网页地址 echo $_SERVER['PHP ...
-
C#: Create a WebRequest with HTTPClient
http://www.cnblogs.com/shanyou/archive/2012/03/21/2410739.html http://msdn.microsoft.com/zh-cn/libra ...
-
3D objects key rendering steps
Key steps of Rendering objects: 1 Create objects’ meshes, which we can use C++’s vector container to ...
-
Android之SurfaceView学习(一)转转
Android之SurfaceView学习(一) 首先我们先来看下官方API对SurfaceView的介绍 SurfaceView的API介绍 Provides a dedicated drawing ...
-
jQuery Mobile 所有class选项,开发全解+完美注释
全栈工程师开发手册 (作者:栾鹏) jQuery Mobile事件全解 jQuery Mobile 所有class选项 jQuery Mobile 所有data-*选项 jQuery Mobile 所 ...
-
最大化等比例测试演化Demo-传统方法
demo-1: <!doctype html> <html> <head> <meta charset="utf-8"> <t ...
-
sklearn机器学习-泰坦尼克号
sklearn实战-乳腺癌细胞数据挖掘(博主亲自录制视频) https://study.163.com/course/introduction.htm?courseId=1005269003& ...
-
安装过redis集群,重新做集群办法:
二:找到问题:这个地方IP的问题,以上是正确的版本,以前有问题的版本的Ip是127.0.0.1, 原因是这个地方以前我没注释redis.conf文件中的bind 127.0.0.1 然后做集群时使用的 ...
-
在 .NET Framework Data Provider for Microsoft SQL Server Compact 3.5 中发生错误
32位机器删除 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\version\DataProviders\{7C602B5B-ACCB-4acd ...
-
python3基础操作
ubuntu下python连接mysql apt-get install python-mysqldb 获取当前时间 >>> from datetime import datetim ...