上一篇咱们对SqlSession和SqlSessionFactory的建立过程有了一个详细的了解,但上述的建立过程只是为SQL执行和SQL映射作了基础的铺垫而已,就和咱们Spring源码为Bean容器的加载进行许多初始化的工做相同,那么作好前期的准备工做接下来该作什么了?该作数据库链接驱动管理和SQL解析工做了!那么本篇本章就来讨论一下数据库驱动链接管理和SQL解析的管理组件之 Executor执行器。java
每个SqlSession都会拥有一个Executor对象,这个对象负责增删改查的具体操做,咱们能够简单的将它理解为JDBC中Statement的封装版。程序员
如图所示,位于继承体系最顶层的是Executor执行器,它有两个实现类,分别是BaseExecutor
和 CachingExecutor
。sql
BaseExecutor
是一个抽象类,这种经过抽象的实现接口的方式是适配器设计模式之接口适配
的体现,是Executor的默认实现,实现了大部分Executor接口定义的功能,下降了接口实现的难度。BaseExecutor的子类有三个,分别是SimpleExecutor
、ReuseExecutor
和BatchExecutor
。数据库
SimpleExecutor: 简单执行器,是MyBatis中默认使用的执行器,每执行一次update或select,就开启一个Statement对象,用完就直接关闭Statement对象(能够是Statement或者是PreparedStatment对象)设计模式
ReuseExecutor: 可重用执行器,这里的重用指的是重复使用Statement,它会在内部使用一个Map把建立的Statement都缓存起来,每次执行SQL命令的时候,都会去判断是否存在基于该SQL的Statement对象,若是存在Statement对象而且对应的connection尚未关闭的状况下就继续使用以前的Statement对象,并将其缓存起来。由于每个SqlSession都有一个新的Executor对象,因此咱们缓存在ReuseExecutor上的Statement做用域是同一个SqlSession。缓存
BatchExecutor: 批处理执行器,用于将多个SQL一次性输出到数据库session
CachingExecutor
: 缓存执行器,先从缓存中查询结果,若是存在,就返回;若是不存在,再委托给Executor delegate 去数据库中取,delegate能够是上面任何一个执行器mybatis
上面咱们分析完SqlSessionFactory的建立过程的准备工做后,咱们下面就开始分析会话的建立以及Executor的执行过程。app
在建立完SqlSessionFactory以后,调用其openSession
方法:
SqlSession sqlSession = factory.openSession();
SqlSessionFactory的默认实现是DefaultSqlSessionFactory,因此咱们须要关心的就是DefaultSqlSessionFactory中的openSession()方法
openSession调用的是openSessionFromDataSource
方法,传递执行器的类型,方法传播级别,是否自动提交,而后在openSessionFromDataSource方法中会建立一个执行器
public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); } private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { // 获得configuration 中的environment final Environment environment = configuration.getEnvironment(); // 获得configuration 中的事务工厂 final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); // 获取执行器 final Executor executor = configuration.newExecutor(tx, execType); // 返回默认的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(); } }
调用newExecutor方法,根据传入的ExecutorType类型来判断是哪一种执行器,而后执行相应的逻辑
public Executor newExecutor(Transaction transaction, ExecutorType executorType) { // defaultExecutorType默认是简单执行器, 若是不传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); } // 若是容许缓存,则使用缓存执行器 // 默认是true,若是不容许缓存的话,须要手动设置 if (cacheEnabled) { executor = new CachingExecutor(executor); } // 插件开发。 executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
ExecutorType的选择:
ExecutorType来决定Configuration对象建立何种类型的执行器,它的赋值能够经过两个地方进行赋值:
- 能够经过
标签来设置当前工程中全部的SqlSession对象使用默认的Executor <settings> <!--取值范围 SIMPLE, REUSE, BATCH --> <setting name="defaultExecutorType" value="SIMPLE"/> </settings>
- 另一种直接经过Java对方法赋值的方式
session = factory.openSession(ExecutorType.BATCH);ExecutorType是一个枚举,它只有三个值SIMPLE, REUSE, BATCH
建立完成Executor以后,会把Executor执行器放入一个DefaultSqlSession对象中来对四个属性进行赋值,他们分别是 configuration
、executor
、 dirty
、autoCommit
。
Executor接口的方法仍是比较多的,这里咱们就几个主要的方法和调用流程来作一个简单的描述
Executor中的大部分方法的调用链实际上是差很少的,下面都是深刻源码分析执行过程,若是你没有时间或者暂时不想深刻研究的话,给你下面的执行流程图做为参考。
query方法有两种形式,一种是直接查询;一种是从缓存中查询,下面来看一下源码
<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;
当有一个查询请求访问的时候,首先会通过Executor的实现类CachingExecutor,先从缓存中查询SQL是不是第一次执行,若是是第一次执行的话,那么就直接执行SQL语句,并建立缓存,若是第二次访问相同的SQL语句的话,那么就会直接从缓存中提取
CachingExecutor.j
// 第一次查询,并建立缓存 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
MapperStatement
维护了一条<select|update|delete|insert>节点的封装,包括资源(resource),配置(configuration),SqlSource(sql源文件)等。使用Configuration的getMappedStatement方法来获取MappedStatement对象
BoundSql
这个类包括SQL的基本信息,基本的SQL语句,参数映射,参数类型等
上述的query方法会调用到CachingExecutor类中的query查询缓存的方法
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { // 获得缓存 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) { list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); } return list; } } // 委托模式,交给SimpleExecutor等实现类去实现方法。 return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
由delegate执行query方法,delegate便是BaseExecutor,而后由具体的执行器去真正执行query方法
注意:源码中通常以do** 开头的方法都是真正加载执行的方法
// 通过一系列的调用,会调用到下面的方法(与主流程无关,故省略) // 以SimpleExecutor简单执行器为例 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,解析SQL语句 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = prepareStatement(handler, ms.getStatementLog()); // 由handler来对SQL语句执行解析工做 return handler.<E>query(stmt, resultHandler); } finally { closeStatement(stmt); } }
由上面的源码能够看出,Executor执行器所起的做用至关因而管理StatementHandler 的整个生命周期的工做,包括建立、初始化、解析、关闭。
ReuseExecutor完成的doQuery 工做:几乎和SimpleExecutor完成的工做同样,其内部不过是使用一个Map来存储每次执行的查询语句,为后面的SQL重用做准备。
BatchExecutor完成的doQuery 工做:和SimpleExecutor完成的工做同样。
在分析完上面的查询方法后,咱们再来聊一下update()方法,update()方法不只仅指的是update()方法,它是一条update链,什么意思呢?就是*insert、update、delete在语义上其实都是更新的意思,而查询在语义上仅仅只是表示的查询,那么咱们来偷窥一下update方法的执行流程,与select的主要执行流程很类似,因此一次性贴出。
// 首先在顶级接口中定义update 方法,交由子类或者抽象子类去实现 // 也是首先去缓存中查询是否具备已经执行过的相同的update语句 public int update(MappedStatement ms, Object parameterObject) throws SQLException { flushCacheIfRequired(ms); return delegate.update(ms, parameterObject); } // 而后再交由BaseExecutor 执行update 方法 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); } // 每每do* 开头的都是真正执行解析的方法,因此doUpdate 应该就是真正要执行update链的解析方法了 // 交给具体的执行器去执行 public int doUpdate(MappedStatement ms, Object parameter) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null); stmt = prepareStatement(handler, ms.getStatementLog()); return handler.update(stmt); } finally { closeStatement(stmt); } }
ReuseExecutor完成的doUpdate 工做:几乎和SimpleExecutor完成的工做同样,其内部不过是使用一个Map来存储每次执行的更新语句,为后面的SQL重用做准备。
BatchExecutor完成的doUpdate 工做:和SimpleExecutor完成的工做类似,只是其内部有一个List列表来一次行的存储多个Statement,用于将多个sql语句一次性输送到数据库执行.
咱们查阅其源码的时候,在执行器的执行过程当中并无发现其与query方法有任何不一样之处,可是在doQueryCursor 方法中咱们能够看到它返回了一个cursor对象,网上搜索cursor的相关资料并查阅其基本结构,得出来的结论是:用于逐条读取SQL语句,应对数据量
// 查询能够返回Cursor<T>类型的数据,相似于JDBC里的ResultSet类, // 当查询百万级的数据的时候,使用游标能够节省内存的消耗,不须要一次性取出全部数据,能够进行逐条处理或逐条取出部分批量处理。 public interface Cursor<T> extends Closeable, Iterable<T> { boolean isOpen(); boolean isConsumed(); int getCurrentIndex(); }
flushStatement()的主要执行流程和query,update 的执行流程差很少,咱们这里就再也不详细贴代码了,简单说一下flushStatement()的主要做用,flushStatement()主要用来释放statement,或者用于ReuseExecutor和BatchExecutor来刷新缓存
createCacheKey()方法主要由BaseExecutor来执行并建立缓存,MyBatis中的缓存分为一级缓存和二级缓存,关于缓存的讨论咱们将在Mybatis系列的缓存章节
Executor 中还有其余方法,提交commit,回滚rollback,判断是否时候缓存isCached,关闭close,获取事务getTransaction一级清除本地缓存clearLocalCache等
在上面的分析过程当中咱们了解到,Executor执行器是MyBatis中很重要的一个组件,Executor至关因而外包的boss,它定义了甲方(SQL)须要干的活(Executor的主要方法),这个外包公司是个小公司,没多少人,每一个人都须要干不少工做,boss接到开发任务的话,通常都找项目经理(CachingExecutor),项目经理几乎不懂技术,它主要和技术leader(BaseExecutor)打交道,技术leader主要负责框架的搭建,具体的工做都会交给下面的程序员来作,程序员的技术也有优劣,高级程序员(BatchExecutor)、中级程序员(ReuseExecutor)、初级程序员(SimpleExecutor),它们干的活也不同。通常有新的项目需求传达到项目经理这里,项目经理先判断本身手里有没有现成的类库或者项目直接套用(Cache),有的话就直接让技术leader拿来直接套用就好,没有的话就须要搭建框架,再把框架存入本地类库中,再进行解析。
(本文完)
下文预告: MyBatis 核心配置综述之StatementHandler
文章参考:
https://www.jianshu.com/p/19686af69b0d
http://www.mybatis.org/mybatis-3/getting-started.html
https://www.cnblogs.com/virgosnail/p/10067964.html