根据前文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. [leetcode-556-Next Greater Element III]

    Given a positive 32-bit integer n, you need to find the smallest 32-bit integer which has exactly th ...

  2. less中的变量

     [less中的变量]1.声明变量:@变量名:变量值:使用变量:@变量名:[less中变量的类型]1.数字 数字px2.字符串:无引号字符串 red blue 有引号 "haha" ...

  3. SQL SERVER查看索引使用情况

    SELECT DISTINCT DB_NAME() AS N'db_name' , E.name AS N'schema_name' , OBJECT_NAME(a.object_id) AS N't ...

  4. PowerShell使用-debug定位问题

    PowerShell就像它的名字一样,很强大,用起来很方便,所以微软基本上所有的主流企业级产品都支持PowerShell,Azure也不例外.通过Azure门户网站固然是简单直观,但对于很多IT管理员 ...

  5. 【Python3之多进程】

    一.进程和线程的简单解释 进程(process)和线程(thread)是操作系统的基本概念,但是它们比较抽象,不容易掌握. 用生活举例: (转自阮一峰网络日志) 1.计算机的核心是CPU,它承担了所有 ...

  6. 基于Spring MVC 实现拦截器

    Spring MVC 拦截器 一,具体内容: 在所有的开发之中拦截器属于一个重要的组件,可以说几乎所有的项目都会提供的概念应用,不管是Spring MVC,还是Struts 2.x都是提供有拦截器的, ...

  7. asp.net core高级应用:TagHelper+Form

    上一篇博客我讲解了TagHelper的基本用法和自定义标签的生成,那么我就趁热打铁,和大家分享一下TagHelper的高级用法~~,大家也可以在我的博客下随意留言. 对于初步接触asp.net cor ...

  8. 面向对象15.3String类-常见功能-获取-1

    API使用: 查API文档的时候,有很多方法,首先先看返回的类型 下面的方法函数有的是有覆写Object类的如1.1图,如果没有复写的话是写在1.2图片那里的,如果找到了相对于的方法,可以点击进去可以 ...

  9. fedora下一些问题的解决方案汇总

    解决fedora下一些使用问题 一 解决fedora下无法使用Fn+功能键来调整亮度的问题 在fedora下,背光的配置参数在/sys/class/backlight文件夹下,根据不同的显卡,有不同的 ...

  10. SVN常见问题

    one or more files are in a conflicted state.(一个或多个文件处于矛盾状态)意思是这个文件已经被其他人修改过了. 然后我点击ok按钮后,找到冲突的文件再次up ...