根据前文Mybatis源码分析-SqlSessionTemplate的简单分析,对于SqlSession的CURD操作都需要经过Executor接口的update/query方法,本文将分析下BaseExecutor如何解析执行sql语句

BaseExecutor-抽象类

其是Executor接口的实现类但为抽象类,另外一个则为具体实现类为CachingExecutor,主要是通过装饰器的设计模式在原来的executor上再附上缓存的属性,有兴趣的可自行查阅。先从构造函数看一发

  protected BaseExecutor(Configuration configuration, Transaction transaction) {
//事务对象
this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();
this.localCache = new PerpetualCache("LocalCache");
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
//表明exector的状态
this.closed = false;
//主文件属性,主要获取MappedStatement对象
this.configuration = configuration;
this.wrapper = this;
}

BaseExecutor#update()-SqlSession之insert/update/delete入口

具体源码如下

  @Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 清除本地缓存,基于SqlSession范围作用
clearLocalCache();
//供子类复写执行CUD操作
return doUpdate(ms, parameter);
}

BaseExecutor#query()-SqlSession之select入口

具体源码如下

  @Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//获取绑定的sql,并将参数对象与sql语句的#{}一一对应
BoundSql boundSql = ms.getBoundSql(parameter);
//获取cacheKey供缓存,包含完整的语句、参数等,确保CacheKey的唯一性
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
//比原先多传入CacheKey和BoundSql参数
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

具体的查询处理逻辑如下

  @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) {
//此处尝试对Callable类型的表达式进行处理,主要是针对mode=out类型的参数
//此参数主要是通过map来定义,直接从map中获取
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//从数据库中获取并进行缓存处理,其也会调用子类需复写的doQuery()方法
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) {
// issue #482
clearLocalCache();
}
}
return list;
}

由上可知,BaseExecutor对CRUD操作均转化为对子类的doUpdate()/doQuery()方法的调用,并一般都会相应的结果进行缓存以免频繁请求数据库导致性能下降(称之为一级缓存)。本文则从SimpleExecutor子类来进行分析

SimpleExecutor

分别看下SimpleExecutor复写的doUpdate()和doQuery()方法,具体源码如下

doUpdate()

  @Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Configuration configuration = ms.getConfiguration();
//创建StatementHandler来处理update()
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
//创建表达式对象Statement
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
}

doQuery()

  @Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();
//创建StatementHandler来处理query()
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//创建表达式对象Statement
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
}

doUpdate()/doQuery()代码的执行逻辑一致,均是先创建StatementHandler对象,然后通过prepareStatement()方法创建表达式对象,供前者调用处理update/query方法

SimpleExecutor#prepareStatement()-创建预表达式对象

逻辑如下

  //handler对象对应的为RoutingStatementHandler对象,其实也是个适配管理类
//可根据MappedStatement的statementType来确定表达式处理handler类,后续讲解
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//获取连接对象,如果该日志等级为debug,则会打印相应的处理日志,采用代理实现
Connection connection = getConnection(statementLog);
//创建真实的Statement对象,比如SimpleStatement/PreparedStatement/CallableStatement
stmt = handler.prepare(connection, transaction.getTimeout());
//请求参数,常用在preparedStatement用来设置相应的请求参数
handler.parameterize(stmt);
return stmt;
}

关于缓存

这里的org.apache.ibatis.executor.BaseExecutor以及org.apache.ibatis.executor.CachingExecutor在执行相应的SQL语句查询前都会进行一次相应的缓存处理。前者称之为一级缓存,后者称之为二级缓存

什么意思呢???


一级缓存

首先有必要先解释下一级缓存,从上文的代码中可以发现其就是针对相同的SQL查询,会优先从本地缓存查询,如果没有再从数据库查询;如果对应的SqlSession一旦有CUD操作,则SqlSession内的本地缓存将被重新清除,下一次的R操作则必须从数据库中读取了~~~~

由此可以得知,mybatis的一级缓存是基于SqlSession的,不同的SqlSession针对相同的SQL操作有可能得到的返回结果是不一样。且一旦有更新操作,则一级缓存缓存的所有结果集都被清空~~~


二级缓存

再而对二级缓存作下解释,其是由CachingExecutor在执行query()方法的时候会再加一个判断,笔者把代码贴出来好针对性的理解

  @Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 针对MappedStatement级别获取Cache对象
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
// query操作一般此判断均通过 ResultHandler一般DAO接口不会定义此入参
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
// 从mappedStatement的cache对象中获取对应的结果集
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
// 为空则还是尝试通过一级缓存去获取
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 放入对应的cache中(当Sqlsession执行commit()操作时则塞入)
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

可以发现,二级缓存是基于namespace作用域来的,也就是MappedStatement级别。一旦有对应的MappedStatement对象执行了CUD操作则会清空MappedStatement级别对应的Cache,而不影响其它MappedStatement的对象。

举例子,也就是说针对用户表以及用户具体信息两张表的MappedStatement,在开启二级缓存的时候就是独立而互相不影响的~~~这或许会造成不一致的现象


写下简短的结论

1.一级缓存作用于SqlSession,默认是开启的

2.二级缓存作用于MappedStatement,而其则需要在对应的mapper文件下添加<cache />标签才可生效,并且每个CURD标签都可以使用useCache属性来决定是否使用二级缓存(默认是TRUE~)

3.二级缓存作用于MappedStatement,所以其不能对细粒度高的场景不适合,也就是前文提及的可能不一致的场景也就是二级缓存默认是不开启的如果更新操作少而查询操作多的场景则可以考虑采取二级缓存提升性能哦~~~

小结

  1. BaseExecutor抽象类提供了对CRUD操作的入口,并带有缓存效应,子类只需要复写doUpdate()和doQuery()抽象方法即可

  2. 在生成Statement对象来执行SQL时对应的池的不同反应

    SimpleExecutor-简单的处理实现类,即基本每次对相同的sql语句都会创建新的Statement对象;ReuseExecutor-复用处理实现类,即对相同的sql语句会缓存Statement对象;BatchExecutor-批处理实现类

  3. 最终获取Statement对象以及执行sql语句的解释权在于StatementHandler接口,详情看下节内容

Mybatis源码分析-BaseExecutor的更多相关文章

  1. Mybatis源码分析-StatementHandler

    承接前文Mybatis源码分析-BaseExecutor,本文则对通过StatementHandler接口完成数据库的CRUD操作作简单的分析 StatementHandler#接口列表 //获取St ...

  2. MyBatis源码分析-SQL语句执行的完整流程

    MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...

  3. MyBatis源码分析(4)—— Cache构建以及应用

    @(MyBatis)[Cache] MyBatis源码分析--Cache构建以及应用 SqlSession使用缓存流程 如果开启了二级缓存,而Executor会使用CachingExecutor来装饰 ...

  4. 【MyBatis源码分析】select源码分析及小结

    示例代码 之前的文章说过,对于MyBatis来说insert.update.delete是一组的,因为对于MyBatis来说它们都是update:select是一组的,因为对于MyBatis来说它就是 ...

  5. MyBatis 源码分析 - 缓存原理

    1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 Redis 或 memcached 等缓存中间件,拦截大量奔向数据库的请求,减轻数据库压力.作为一个重要的组件,MyBatis 自然 ...

  6. Mybatis源码分析

    MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...

  7. Mybatis源码分析之Cache二级缓存原理 (五)

    一:Cache类的介绍 讲解缓存之前我们需要先了解一下Cache接口以及实现MyBatis定义了一个org.apache.ibatis.cache.Cache接口作为其Cache提供者的SPI(Ser ...

  8. MyBatis源码分析(各组件关系+底层原理

    MyBatis源码分析MyBatis流程图 下面将结合代码具体分析. MyBatis具体代码分析 SqlSessionFactoryBuilder根据XML文件流,或者Configuration类实例 ...

  9. 精尽MyBatis源码分析 - MyBatis 的 SQL 执行过程(一)之 Executor

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

随机推荐

  1. ajax数据请求3(数组json格式)

    ajax数据请求3(数组json格式) <!doctype html> <html> <head> <meta charset="utf-8&quo ...

  2. 异常:java.lang.NoSuchMethodError: org.apache.poi.ss.usermodel.Workbook.getCellStyleAt

    背景 最近公司所有新项目要使用最新高效快速开发框架nature-framework,框架本身结合NatureMap已经集成excel的高效导入功能,我们要实现高性能的导出功能,因为最新的jxls-2. ...

  3. jquery 变量和原生js变量的关系

    其实js 变量和 jquery没什么不一样, 也可以直接 var  hhhh=$("header"); 但是当用到用到hhh时依然要用jquery 的方式,而不能js原生的方式.

  4. window.onload的使用心得

    如果我问你window.onload是什么意思,恐怕你会回答我:"这不是页面加载完就执行吗".  但是答案是不一定,得看你怎么用.看一下例子吧 例1:  代码如下:   <! ...

  5. JavaWeb 后端 <十四> 文件上传下载

    1.文件上传与下载 案例: 注册表单/保存商品等相关模块! --à 注册选择头像 / 商品图片 (数据库:存储图片路径 / 图片保存到服务器中指定的目录) 1.1 文件上传 文件上传,要点: 前台: ...

  6. php之试触法----error--关键字的误用

    实际开发中,在不同网页的输出中,常常有许多公共的代码或者变量需要使用,于是定义了以下类来缩减代码量 如下代码所示: <?php class universalClass { function w ...

  7. js获取鼠标点击的对象,点击另一个按钮删除该对象

    作为js的一名新手,对于所谓的event的了解并不是太多,仅仅根据视频教学中的例子模仿着,写了诸如: function funcname(e) { e=window.event||event };的函 ...

  8. 使用jersey 注解包扫描类PackageNamesScanner

    Jersey 中自带一个包扫描,可以是包,或者具体类名 ,扫描的类型是自己定注解类型,实现功能更加大,可以是jar 包 可以是虚拟地址下的 Jersey 主要用来扫描Path Provider 类中同 ...

  9. JS - 实现简单易用的倒计时 x 天 x 时 x 分 x 秒

    <script> (function () { var tian = document.getElementsByClassName('JS-tian')[0]; var shi = do ...

  10. 大数据Python学习大纲

    最近公司在写一个课程<大数据运维实训课>,分为4个部分,linux实训课.Python开发.hadoop基础知识和项目实战.这门课程主要针对刚从学校毕业的学生去应聘时不会像一个小白菜一样被 ...