1.Executor 主体结构

Executor是MyBatis执行者接口,执行器的功能包括:

  • 基本功能:改、查,没有增删的原因是,所有的增删操作都可以归结到改。
  • 缓存维护:这里的缓存主要是为一级缓存服务,功能包括创建缓存Key、清理缓存、判断缓存是否存在。
  • 事物管理:提交、回滚、关闭、批处理刷新。

Executor 的生命周期和 SqlSession 是一样的,之所以要明确的指出这一点是因为 Executor 中包含了缓存的处理,并且因为 SqlSession 是线程不安全的。

对于这个接口MyBatis是有三个实现子类。分别是

  • SimpleExecutor(简单执行器)

  • ReuseExecutor(重用执行器)

  • BatchExecutor(批处理执行器)。

    public interface Executor {ResultHandler NO_RESULT_HANDLER = null;int update(MappedStatement ms, Object parameter) throws SQLException;<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;<E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;List<BatchResult> flushStatements() throws SQLException;void commit(boolean required) throws SQLException;void rollback(boolean required) throws SQLException;CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);boolean isCached(MappedStatement ms, CacheKey key);void clearLocalCache();void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);Transaction getTransaction();void close(boolean forceRollback);boolean isClosed();void setExecutorWrapper(Executor executor);}
    

1.1Executor类结构

其各自的功能:

  • BaseExecutor:基础执行器,封装了子类的公共方法,包括一级缓存、延迟加载、回滚、关闭等功能;
  • SimpleExecutor:简单执行器,每执行一条 sql,都会打开一个 Statement,执行完成后关闭;
  • ReuseExecutor:重用执行器,相较于 SimpleExecutor 多了 Statement 的缓存功能,并使用SQL语句作为Key,其内部维护一个 Map<String, Statement>,每次编译完成的 Statement 都会进行缓存,不会关闭;
  • BatchExecutor:批量执行器,基于 JDBC 的 addBatch、executeBatch 功能,并且在当前 sql 和上一条 sql 完全一样的时候,重用 Statement,在调用 doFlushStatements 的时候,将数据刷新到数据库;
  • CachingExecutor:缓存执行器,装饰器模式,在开启二级缓存的时候。会在上面三种执行器的外面包上 CachingExecutor;

1.2创建Executor

Executor会根据ExecutorType的类型进行创建,如果mybatis的config配置文件开启了二级缓存(cacheEnabled=true)则会使用装饰器模式创建一个CachingExecutor来维护二级缓存。后面会详细介绍

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 {executor = new SimpleExecutor(this, transaction);}// 开启二级缓存,使用装饰器模式if (cacheEnabled) {executor = new CachingExecutor(executor);}// mybatis插件相关  executor = (Executor) interceptorChain.pluginAll(executor);return executor;
}

2.BaseExecutor

BaseExecutor 基础执行器主要是用于维护一级缓存和事物。事物是通过会话中调用commit、rollback进行管理。

重点在于一级缓存这块它是如何处理的?

它实现了Executor中的Query与update方法。SqlSession会话中SQL请求,正是调用的这两个方法。Query方法中处理一级缓存逻辑,即根据SQL及参数判断缓存中是否存在数据,有就走缓存。否则就会调用子类的doQuery() 方法去查询数据库,然后在设置缓存。在doUpdate() 中主要是用于清空缓存。

2.1query

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {// 获取绑定的sqlBoundSql boundSql = ms.getBoundSql(parameter);// 缓存key  CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
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.");}// mapperxml文件FlushCache 配置为true,会清除缓存。不使用缓存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 #601deferredLoads.clear();// 一级缓存本身不能关闭  // mybatis-config的setting节点设置一级缓存LocalCacheScope为STATEMENT也会清除缓存// <setting name="localCacheScope" value="STATEMENT"/>  if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {// issue #482clearLocalCache();}}return list;
}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {List<E> list;localCache.putObject(key, EXECUTION_PLACEHOLDER);try {// 真正的去数据库查询数据  list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);} finally {// 先清除缓存  localCache.removeObject(key);}// 查询结果加入到缓存中  localCache.putObject(key, list);if (ms.getStatementType() == StatementType.CALLABLE) {localOutputParameterCache.putObject(key, parameter);}return list;
}

2.2查询

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.");}// 清除缓存  clearLocalCache();return doUpdate(ms, parameter);
}

2.3模板方法

这里就是一个典型的模版方法模式了,子类都会实现自己模版方法;

protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException;protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)throws SQLException;protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)throws SQLException;

2.4事务相关

@Override
public void close(boolean forceRollback) {try {try {// close之前,先回滚事务。// connection没有设置自动提交,新增数据后没有commit。连接close之前,需要把数据回滚掉  rollback(forceRollback);} finally {if (transaction != null) {transaction.close();}}} catch (SQLException e) {// Ignore. There's nothing that can be done at this point.log.warn("Unexpected exception on closing transaction.  Cause: " + e);} finally {transaction = null;deferredLoads = null;localCache = null;localOutputParameterCache = null;closed = true;}
}public void commit(boolean required) throws SQLException {if (closed) {throw new ExecutorException("Cannot commit, transaction is already closed");}// 提交会清除一级缓存及刷新StatementsclearLocalCache();flushStatements();if (required) {transaction.commit();}}@Overridepublic void rollback(boolean required) throws SQLException {if (!closed) {try {// 回滚会清除一级缓存及刷新Statements  clearLocalCache();flushStatements(true);} finally {if (required) {transaction.rollback();}}}}

3.SimpleExecutor

SimpleExecutor是默认执行器,它的行为是每处理一次会话当中的SQl请求都会通过对应的StatementHandler 构建一个新个Statement,这就会导致即使是相同SQL语句也无法重用Statement。每次打开一个 Statement,使用完成以后关闭。后续的处理交由StatementHandler进行增删改查

@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();// 构建StatementHandlerStatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);// 构建新的Statementstmt = prepareStatement(handler, ms.getStatementLog());return handler.update(stmt);} finally {// 关闭Statement,同JDBC经典用法  closeStatement(stmt);}
}@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);stmt = prepareStatement(handler, ms.getStatementLog());return handler.query(stmt, resultHandler);} finally {closeStatement(stmt);}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statement stmt;// 获取连接,在Baseexecutor中,从Transaction中获取Connection connection = getConnection(statementLog);stmt = handler.prepare(connection, transaction.getTimeout());handler.parameterize(stmt);return stmt;
}

4.ReuseExecutor

ReuseExecutor 区别在于他会将在会话期间内的Statement进行缓存,并使用SQL语句作为Key。所以当执行下一请求的时候,不在重复构建Statement,而是从缓存中取出并设置参数,然后执行。

这也说明为啥执行器不能跨线程调用,这会导致两个线程给同一个Statement 设置不同场景参数。

// 缓存Statement的Map
private final Map<String, Statement> statementMap = new HashMap<>();private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statement stmt;// 获取绑定的sql  BoundSql boundSql = handler.getBoundSql();String sql = boundSql.getSql();// 如果缓存中已经有了,直接得到Statement  if (hasStatementFor(sql)) {// 从缓存中获取Statementstmt = getStatement(sql);applyTransactionTimeout(stmt);} else {// 如果缓存没有,就编译一个然后加入缓存Connection connection = getConnection(statementLog);stmt = handler.prepare(connection, transaction.getTimeout());// 放入缓存中,下次使用  putStatement(sql, stmt);}handler.parameterize(stmt);return stmt;
}private boolean hasStatementFor(String sql) {try {Statement statement = statementMap.get(sql);// 缓存Statement存在且连接Connection没有关闭return statement != null && !statement.getConnection().isClosed();} catch (SQLException e) {return false;}
}

发现ReuseExecutor没有去手动关闭closeStatement。当没有设置connection为自动提交时,我们需要手动commit,close,rollback。会间接调用doFlushStatements来关闭Statements。

如果设置connection为AutoCommit时,我们需要自己调用doFlushStatements来关闭Statements。

public List<BatchResult> doFlushStatements(boolean isRollback) {for (Statement stmt : statementMap.values()) {closeStatement(stmt);}statementMap.clear();return Collections.emptyList();
}

5.BatchExecutor

BatchExecutor 顾名思议,它就是用来作批处理的。但会将所 有SQL请求集中起来,最后调用Executor.flushStatements() 方法时一次性将所有请求发送至数据库。

BachExecutor 是基于 JDBC 的 addBatch、executeBatch 功能的执行器,所以 BachExecutor 只能用于更新(insert|delete|update),不能用于查询(select)。doQuery与SimpleExecutor一致。

// 待处理的 Statement
private final List<Statement> statementList = new ArrayList<>();
// 对应的结果集
private final List<BatchResult> batchResultList = new ArrayList<>();
// 上一次执行 sql
private String currentSql;
// 上次执行的 MappedStatement
private MappedStatement currentStatement;public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {final Configuration configuration = ms.getConfiguration();final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);final BoundSql boundSql = handler.getBoundSql();// 本次执行的 sqlfinal String sql = boundSql.getSql();final Statement stmt;// 当本次执行的 sql 和 MappedStatement 与上次的相同时,直接复用上一次的 Statementif (sql.equals(currentSql) && ms.equals(currentStatement)) {int last = statementList.size() - 1;stmt = statementList.get(last);applyTransactionTimeout(stmt);handler.parameterize(stmt);// fix Issues 322BatchResult batchResult = batchResultList.get(last);batchResult.addParameterObject(parameterObject);} else {// 不同时,新建 Statement,并加入缓存Connection connection = getConnection(ms.getStatementLog());stmt = handler.prepare(connection, transaction.getTimeout());handler.parameterize(stmt);    // fix Issues 322currentSql = sql;currentStatement = ms;statementList.add(stmt);batchResultList.add(new BatchResult(ms, sql, parameterObject));}// 添加批处理任务handler.batch(stmt);// 注意这里返回的不再是更新的行数,而是一个常量return BATCH_UPDATE_RETURN_VALUE;
}

数据什么更新到数据库呢?

处理 update 的任何操作,包括 commit、close等任何操作,具体执行的方法就是 doFlushStatements此外需要注意的是 Batch 方式插入使用 useGeneratedKeys 获取主键,在提交完任务之后,并不能马上取到,因为此时 sql 语句还在缓存中没有真正执行,当执行完 Flush 之后,会通过回调的方式反射设置主键;

public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {try {List<BatchResult> results = new ArrayList<>();if (isRollback) {return Collections.emptyList();}// 循环处理statement的executeBatchfor (int i = 0, n = statementList.size(); i < n; i++) {Statement stmt = statementList.get(i);applyTransactionTimeout(stmt);BatchResult batchResult = batchResultList.get(i);try {// 更新影响的结果数batchResult.setUpdateCounts(stmt.executeBatch());MappedStatement ms = batchResult.getMappedStatement();List<Object> parameterObjects = batchResult.getParameterObjects();KeyGenerator keyGenerator = ms.getKeyGenerator();// insert返回主键,设置进实体对象中。会单独分析if (Jdbc3KeyGenerator.class.equals(keyGenerator.getClass())) {Jdbc3KeyGenerator jdbc3KeyGenerator = (Jdbc3KeyGenerator) keyGenerator;jdbc3KeyGenerator.processBatch(ms, stmt, parameterObjects);} else if (!NoKeyGenerator.class.equals(keyGenerator.getClass())) { //issue #141for (Object parameter : parameterObjects) {keyGenerator.processAfter(this, ms, stmt, parameter);}}// Close statement to close cursor #1109// 处理结束后关闭statementcloseStatement(stmt);} catch (BatchUpdateException e) {StringBuilder message = new StringBuilder();message.append(batchResult.getMappedStatement().getId()).append(" (batch index #").append(i + 1).append(")").append(" failed.");if (i > 0) {message.append(" ").append(i).append(" prior sub executor(s) completed successfully, but will be rolled back.");}throw new BatchExecutorException(message.toString(), e, results, batchResult);}results.add(batchResult);}// 返回处理结果,从BatchResult.updateCounts 获取更新的条数return results;} finally {for (Statement stmt : statementList) {closeStatement(stmt);}currentSql = null;statementList.clear();batchResultList.clear();}}

只有连续相同的SQL语句并且相同的SQL映射声明,才会重用Statement,并利用其批处理功能。否则会构建一个新的Satement然后在flushStatements() 时一次执行。这么做的原因是它要保证执行顺序,跟调用顺序一至。

假设上图中相同的线条颜色,就是相同的SQL语句。为了保证执行顺序只有绿色线条合并成一个Statement而两条黄线不能,否则就会导致,后面的黄线先于中间的绿线执行,有违调用顺序。这中情况会创建3个Satement,两天sql语句之间有 其他的sql语句。

6.CachingExecutor

CachingExecutor缓存执行器,用于处理二级缓存的。二级缓存和一级缓存相对独立的逻辑,而且二级缓存可以通过参数控制关闭,而一级缓存是不可以的。综上原因把二级缓存单独抽出来处理。抽取的方式采用了装饰者设计模式,即在CachingExecutor 对原有的执行器进行包装,处理完二级缓存逻辑之后,把SQL执行相关的逻辑交给实至的Executor处理。

7.执行器总结

执行器的种类有:基础执行器、简单执行器、重用执行器和批处理执行器,此外通过装饰器形式添加了一个缓存执行器。对应功能包括缓存处理、事物处理、重用处理以及批处理,这些是多个SQL执行中有共性地方。执行器存在的意义就是去处理这些共性。 如果说每个SQL调用是独立的,不需要缓存,不需要事物也不需集中在一起进行批处理的话,Executor也就没有存在的必要。缓存的具体处理下面会详细分析

个Satement,两天sql语句之间有 其他的sql语句。

6.CachingExecutor

CachingExecutor缓存执行器,用于处理二级缓存的。二级缓存和一级缓存相对独立的逻辑,而且二级缓存可以通过参数控制关闭,而一级缓存是不可以的。综上原因把二级缓存单独抽出来处理。抽取的方式采用了装饰者设计模式,即在CachingExecutor 对原有的执行器进行包装,处理完二级缓存逻辑之后,把SQL执行相关的逻辑交给实至的Executor处理。

[外链图片转存中…(img-HJst6tnF-1630773787493)]

7.执行器总结

执行器的种类有:基础执行器、简单执行器、重用执行器和批处理执行器,此外通过装饰器形式添加了一个缓存执行器。对应功能包括缓存处理、事物处理、重用处理以及批处理,这些是多个SQL执行中有共性地方。执行器存在的意义就是去处理这些共性。 如果说每个SQL调用是独立的,不需要缓存,不需要事物也不需集中在一起进行批处理的话,Executor也就没有存在的必要。缓存的具体处理下面会详细分析

4.Executor执行器相关推荐

  1. JDK Executor执行器的应用

    JDK5 Executor执行器的应用 JDK5新增了一个软件包java.util.concurrent,提供了大量在并发编程中很常用的实用工具类. java.util.concurrent.Exec ...

  2. mybatis都有哪些executor执行器_Mybatis的SqlSession创建过程详解

    前面mybatis的初始化过程分析完成,接下来是第二步SqlSession的创建. 创建过程总览 SqlSession创建过程如下图:创建过程还是比较简单的,首先是之前分析的SqlSessionFac ...

  3. spring 中定时器的 task:executor执行器和调度器task:scheduler

    任务调度器配置:  task:scheduler/@pool-size:调度线程池的大小,调度线程在被调度任务完成前不会空闲  task:scheduled/@cron:cron表达式,注意,若上次任 ...

  4. Executor执行器

    分为四个模块: 1.动态代理MapperProxy 2.SQL会话SqlSesson 3.执行器Executor 4.JDBC处理器StatementHandler 现讲述为SQL会话与执行器 一.S ...

  5. xxl-job dotnet core executor执行器开源

    DotXxlJob [(github)https://github.com/xuanye/DotXxlJob][https://github.com/xuanye/DotXxlJob] xxl-job ...

  6. xxl-job 执行结果是空_xxljob dotnet core executor执行器开源

    DotXxlJob [(github)https://github.com/xuanye/DotXxlJob][https://github.com/xuanye/DotXxlJob] xxl-job ...

  7. Executor 执行器接口笔记

    https://blog.csdn.net/zt011052/article/details/102955048/

  8. MyBatis源码-解读Executor的三个实现类之BatchExecutor(批处理执行器)

    文章目录 Pre Executor 执行器 接口继承关系 BatchExecutor(重用执行器) 入门小demo 源码 BatchExecutor VS ReuseExecutor Pre MyBa ...

  9. MyBatis源码-解读Executor的三个实现类之SimpleExecutor(简单执行器)

    文章目录 Pre Executor 执行器 接口继承关系 SimpleExecutor(简单执行器) 入门小demo 实例化SimpleExecutor doQuery方法 Pre MyBatis源码 ...

最新文章

  1. 微课竞赛系统的设计与实现所需工作条件_工作室文化建设展示(3)
  2. IntelliJ IDEA 修改包名
  3. JAVA——监听器ActionListener与MouseListener执行顺序
  4. linux页表,arm linux 页表(转)
  5. EntityFramework Core动态加载模型,我们要知道些什么呢?
  6. 表格数字乘以百分比怎么算_Excel输入多位数字之后有了“E”,用文本格式一步到位,恢复原状...
  7. 通俗演义TCP流量控制
  8. ubuntu(linux)占领小米平板2(mipad2)
  9. Django 开发收银系统六
  10. 百度竞价推广地域是如何选择的?
  11. 写一个简易的手机APP
  12. Computer OpenCart 自适应主题模板 ABC-0084
  13. 拯救者笔记本 除尘后 开机蓝屏
  14. 中国首善刘强东出手了!给京东高管降薪,给普通员工提高福利!并拿出100亿保障”兄弟“基础住房...
  15. 微信推出“微信圈子”,玩起来原来这么爽?
  16. 实在人做实干型产品——专访凯特伟业CEO云凤程
  17. 用Java写了一个简单的控制台狼人杀游戏
  18. 怎么让Win10不显示快速访问记录
  19. Web的相关概念及BC、CS结构
  20. Java Socket 如何接收byte和String

热门文章

  1. 数字货币量化分析[2018-05-27]
  2. VMware workstation搭建华为FusionCompute实验环境(二)保姆级安装教程,可运行虚拟机
  3. 有哪些能支持epub、txt格式的电子书阅读器?能在MAC上用的?
  4. 华为手机一键修改机器码信息
  5. mysql连接耗尽_避免数据库连接被耗尽的三种配置
  6. angular2后台管理系统
  7. pcolor和surf画出的图形转存为eps或pdf格式出现横竖斜白色虚线网格,完美去掉!!!
  8. Python基础之闭包函数
  9. 19款国产手机无一幸免:15分钟破解人脸识别,打印眼镜让刷脸形同虚设 ?
  10. 分享卖货小程序制作方法_怎么在微信上做小程序卖货