Mybatis源码学习记录(Executor篇)

前言

前文分析了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参数
   List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
  
  // 重载的query方法,没有cacheKey和boundSql参数
   List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

   Cursor queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;

  List 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工具我们看到共有以下几种


Mybatis源码学习记录(Executor篇)_第1张图片
image.png
  • BaseExecutor
  • BatchExecutor
  • CachingExecutor
  • ReuseExecutor
  • SimpleExecutor
  • ClosedExecutor

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

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

Mybatis源码学习记录(Executor篇)_第2张图片
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 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();
    // 初始化一个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  List 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  List 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 list;
    try {
      // 2. 查询堆栈 + 1
      queryStack++;
      // 2.1. 如果此次查询的resultHandler为null(默认为null),则尝试从本地缓存中获取已经缓存的的结果,否则为null
      list = resultHandler == null ? (List) 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 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  List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List 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  List 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  List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List 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  List 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  List 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.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篇))