美文网首页
Mybatis源码学习记录(Executor篇)

Mybatis源码学习记录(Executor篇)

作者: 0爱上1 | 来源:发表于2019-07-26 14:19 被阅读0次

    前言

    前文分析了SqlSession的作用,本文将继续以源码的方式来分析Mybatis中执行器Executor

    Executor.java

    Mybatis的执行器,处理真正的SQL操作

    实际上我们可以这么理解二者的关系,SqlSession是Mybatis的暴露出来给开发人员的最外层API,需要提供细致的方法定义,比如selectOne, selectList等,但是实际上真正执行SQL是需要一个执行器,这个执行器可以不用管你是查单个还是多个,对执行器而言就只是一个query查询而已,再比如SqlSession需要提供如insert/update/delete等细致的api,而对于执行器来说就是一个update方法而已,这就是二者的明显的区别

    接口定义

    public interface Executor {
      // 
      ResultHandler NO_RESULT_HANDLER = null;
    
      // update方法,传入MappedStatement 实例和参数,注意这里是不需要RowBounds参数和ResultHandler的
      int update(MappedStatement ms, Object parameter) throws SQLException;
    
      // query方法,传入MappedStatement,参数,以及rowBounds, resultHandler,注意多了一个cacheKey和boundSql参数
      <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
      
      // 重载的query方法,没有cacheKey和boundSql参数
      <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;
    
      // 缓存相关,创建缓存key
      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);
    }  
    

    接口定义分析完了,总得有人做的,那就看看Executor接口具体有哪些实现类

    利用IDEA工具我们看到共有以下几种


    image.png
    • BaseExecutor
    • BatchExecutor
    • CachingExecutor
    • ReuseExecutor
    • SimpleExecutor
    • ClosedExecutor

    其中BaseExecutor是抽象类,其他实现类(不包括CachingExecutor)则继承该抽象类,以抽象出共同的执行器功能,ClosedExecutor是BaseExecutor的静态内部类,我们不单独分析

    这里先提一下:CachingExecutor是直接实现了Executor接口的,并没有继承BaseExecutor,该Executor是用来实现Mybatis的二级缓存的,后续会以一篇单独的文章来分析Mybatis中的缓存原理

    image.png

    从Mybatis提供的执行器枚举类ExecutorType来看,主要有三种执行器

    • BatchExecutor
    • ReuseExecutor
    • SimpleExecutor

    ExecutorType.emum

    public enum ExecutorType {
      SIMPLE, REUSE, BATCH
    }
    

    我们挑选其中的SimpleExecutor来分析,分析SimpleExecutor之前我们需要先分析其抽象父类BaseExecutor

    BaseExecutor.java

    各种执行器实现的抽象父类,抽象了以上三种执行器的共同部分

    抽象类声明

    public abstract class BaseExecutor implements Executor {...}
    

    共同抽象属性

      private static final Log log = LogFactory.getLog(BaseExecutor.class);
    
      // Mybatis事务实例
      protected Transaction transaction;
      
      // 装饰器模式,指向包装执行器
      protected Executor wrapper;
    
      protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
      
      // 一级缓存相关,PerpetualCache内部就是持有了一个Map集合作为缓存数据的储存
      protected PerpetualCache localCache;
      protected PerpetualCache localOutputParameterCache;
      
      // 持有Mybatis的配置实例
      protected Configuration configuration;
    
      protected int queryStack;
      
      // 代表执行器是否关闭
      private boolean closed;
    

    抽象的共同属性分析完毕,我们看下其构造方法

    构造函数

    protected BaseExecutor(Configuration configuration, Transaction transaction) {
        // 初始化事务属性
        this.transaction = transaction;
        this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();
        // 初始化一个id为LocalCache的缓存对象
        this.localCache = new PerpetualCache("LocalCache");
        // 初始化一个id为LocalOutputParameterCache的缓存对象
        this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
        // 标识自己是否被关闭
        this.closed = false;
        // 初始化配置对象
        this.configuration = configuration;
        // 包装对象先初始化为自己,另外有一个重写的方法setExecutorWrapper可以重置wrapper对象
        this.wrapper = this;
    }
    

    经过以上,我们知道每一个执行器实例都会持有一个事务Transaction,一个mybatis配置属性configuration及Mybatis查询一级缓存相关的PerpetualCache属性和一个包装他的包装执行器实例引用

    接下来我们看下BaseExecutor中到底抽象实现了哪些Executor接口中重要的方法,这其中又用到了哪些设计模式呢?

    我们重点看query方法的实现,其他实现的模式是一样的

      @Override
      public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        // 1. 从MappedStatement对象中根据参数获取BoundSql对象
        BoundSql boundSql = ms.getBoundSql(parameter);
        // 2. 创建缓存Key
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        // 3. 调用重载的query方法
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
     }
    
      @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.");
        }
        // 1. 如果配置了flushCacheRequired为true,则会在执行器执行之前就清空本地一级缓存,换句话说就是关闭一级缓存功能
        if (queryStack == 0 && ms.isFlushCacheRequired()) {
          // 1.1. 查询堆栈为0且需要清空缓存,则执行清空缓存
          clearLocalCache();
        }
        List<E> list;
        try {
          // 2. 查询堆栈 + 1
          queryStack++;
          // 2.1. 如果此次查询的resultHandler为null(默认为null),则尝试从本地缓存中获取已经缓存的的结果,否则为null
          list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
          // 3. 判断是否又已缓存的结果
          if (list != null) {
            // 3.1. 已有缓存结果,则处理本地缓存结果输出参数
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
          } else {
            // 3.2. 没有缓存结果,则从数据库查询结果
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
          }
        } finally {
          // 查询堆栈数 -1
          queryStack--;
        }
        if (queryStack == 0) {
          for (DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
          }
          // issue #601
          deferredLoads.clear();
          if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            // issue #482
            clearLocalCache();
          }
        }
        return list;
      }
    
      @Override
      public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
        if (closed) {
          throw new ExecutorException("Executor was closed.");
        }
        CacheKey cacheKey = new CacheKey();
        cacheKey.update(ms.getId());
        cacheKey.update(rowBounds.getOffset());
        cacheKey.update(rowBounds.getLimit());
        cacheKey.update(boundSql.getSql());
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
        // mimic DefaultParameterHandler logic
        for (ParameterMapping parameterMapping : parameterMappings) {
          if (parameterMapping.getMode() != ParameterMode.OUT) {
            Object value;
            String propertyName = parameterMapping.getProperty();
            if (boundSql.hasAdditionalParameter(propertyName)) {
              value = boundSql.getAdditionalParameter(propertyName);
            } else if (parameterObject == null) {
              value = null;
            } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
              value = parameterObject;
            } else {
              MetaObject metaObject = configuration.newMetaObject(parameterObject);
              value = metaObject.getValue(propertyName);
            }
            cacheKey.update(value);
          }
        }
        if (configuration.getEnvironment() != null) {
          // issue #176
          cacheKey.update(configuration.getEnvironment().getId());
        }
        return cacheKey;
      }
      
      // 从数据库查询数据
      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 {
          // 执行doQuery方法
          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;
      }
      
      // 抽象的doQuery方法,真正的查询由子类实现
      protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
          throws SQLException;
    

    query方法主要有三步:

    1. 根据此次查询参数(如{{"phone", "15800000000"}, {"param1", "15800000000"}}),获取BoundSql,关于BoundSql可以简单看下[夫礼者] 的这篇文章https://blog.csdn.net/lqzkcx3/article/details/78370497
    2. 构建缓存Key,key规则为:msId + rowBounds/offset + rowBounds/limit + boundSql/sql(原参数占位符已被为?的那种) + 实际的传参value值 + environmentId
    3. 执行查询,这里又会细分为两种情况:
      3.1 已存在一级缓存,则直接返回缓存结果
      3.2 不存在缓存,则调用queryFromDatabase方法,从数据库查询

    关于缓存key的生成以及Mybatis一级/二级缓存相关我们会单独以一篇文章来讲解,这里只要知道SimpleExecutor的query方法会先尝试从缓存中获取,如果缓存中没有就从数据库中查询,接下来我们看queryFromDatabase(...)方法

    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        List<E> list;
        // 1. 首先向本地缓存中存入一个ExecutionPlaceholder的枚举类占位value
        localCache.putObject(key, EXECUTION_PLACEHOLDER);
        try {
          // 2. 执行doQuery方法
          list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
          // 3. 执行完成移除这个key?
          localCache.removeObject(key);
        }
        // 4. 将2. 中查询结果存入缓存中
        localCache.putObject(key, list);
        // 5. 如果MappedStatement的类型为CALLABLE,则向localOutputParameterCache缓存中存入value为parameter的缓存
        if (ms.getStatementType() == StatementType.CALLABLE) {
          localOutputParameterCache.putObject(key, parameter);
        }
        return list;
      }
    

    重要的是这里的doQuery方法

    protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
          throws SQLException;
    

    看到这里是否有些明朗了呢?protected修饰的抽象doQuery的方法是交由子类实现的,也就是说真正的查询数据库还是交给了如SimpleExecutor来做

    代码分析到了这里,我们可以看到此处使用了模板方法模式,利用BaseExecutor抽象出来执行器执行SQL所需的通用步骤(比如上面的1,2,3步骤等等),真正的查询那一步则抽象出来,交给子类自己实现,ok,正式进入SimpleExecutor

    SimpleExecutor.java

    Mybatis提供的简单执行器,继承自BaseExecutor

    public class SimpleExecutor extends BaseExecutor {
        // 没有再单独定义任何特有属性,全部继承自BaseExecutor
        
        // 提供一个构造函数,调用父类构造,初始化自父类继承来的属性
        public SimpleExecutor(Configuration configuration, Transaction transaction) {
            super(configuration, transaction);
        }
        
        // 重点doQuery方法实现
        @Override
        public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;
            try {
              // 1. 获取配置实例
              Configuration configuration = ms.getConfiguration();
              // 2. new一个StatementHandler实例
              StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
              // 3. 准备处理器,主要包括创建statement以及动态参数的设置
              stmt = prepareStatement(handler, ms.getStatementLog());
              // 4. 执行真正的数据库操作调用
              return handler.<E>query(stmt, resultHandler);
            } finally {
                // 5. 关闭statement
                closeStatement(stmt);
            }
      }
      
      // 准备Statement
      private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        // 1. 获取代理后(增加日志功能)的Connection对象,这里的getConnection方法是BaseExecutor中的,就是从事务实例中获取Connection实例,并返回增加日志功能后的代理
        Connection connection = getConnection(statementLog);
        
        // 2. 创建Statement对象(可能是一个SimpleStatement,一个PreparedStatement或CallableStatement)
        stmt = handler.prepare(connection, transaction.getTimeout());
        
        // 3. 参数化处理,注意SimpleStatement是静态SQL执行,不需要处理入参的
        handler.parameterize(stmt);
        
        // 4. 返回执行前最后准备好的Statement对象
        return stmt;
      }
    }
    

    整个SimpleExecutor的doQuery方法可以简述为以下几步

    1. 第一步就是通过MappedStatement实例获取configuration实例

    2. 传入一大堆参数以后,new出来一个StatementHandler的实例

    3. 私有方法prepareStatement(...),就是创建了真正的JDBC Statement实例(3.1. 调用handler.prepare(...)创建Statement)并对其进行参数化处理(3.2. 调用handler.parameterize(...)参数化处理Statement,需要注意的是这里的参数化处理实际上是委托给了handler内部持有的ParamterHandler来做的)

    4. Statement有了,参数也处理好了(?号已经被设置上真实的入参了),最后就是调用handler.query(...)方法执行数据库查询操作,再由resultSetHandler处理查询出的结果后返回

    总结

    本文只是简单的介绍Mybatis中的Executor接口的执行逻辑,并没有深入分析关于一级/二级缓存方面的源码,关于缓存方面后续会单独一篇文章讲解

    简单总结一下本文重点知识:
    1:每个SqlSession内部都持有一个Executor执行器实例,SqlSession会将数据库执行操作委托给执行器来做

    2:Mybatis将执行器分为三种SIMPLE, REUSEBATCH,并为其抽象出一层BaseExecutor抽象类,其中定义了三种执行器通用的处理逻辑以及共有属性,而真正三种执行器不同的地方则由自己实现,有点类似模板方法模式的感觉,我们需要记住的是不论哪种执行器,其最终都是要调用数据库操作的,也就是说其内部均持有一个Transaction实例,而Transaction内部则持有真正的JDBC Connection对象

    3:拿SimpleExecutor来说,其doQuery查询数据库操作来说,需要首先创建一个StatementHandler,利用这个handler去执行创建Statement实例,参数化Statement数据库执行操作以及最终将结果处理后返回

    相关文章

      网友评论

          本文标题:Mybatis源码学习记录(Executor篇)

          本文链接:https://www.haomeiwen.com/subject/niorrctx.html