Mybais 源码分析三 Executor

Mybatis的执行器Executor,在创建SqlSession时由SqlSessionFactory注入到SqlSession中。SqlSession把执行sql和返回结果集委托给了Executor,Executor有3中类型 : SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新,我们在mybatis核心配置文件< setting>节点中可以配置默认的执行器

<settings>
  <setting name="defaultExecutorType" value="SIMPLE"/>
</settings>

在SqlsessionFactory中,由Configuration取得Executor注入到SqlSession中,详细请看Mybatis 源码分析一 SqlSessionFactory

 final Executor executor = configuration.newExecutor(tx, execType);
 return new DefaultSqlSession(configuration, executor, autoCommit);

execType是一个枚举实例,XMLConfigBuilder读取< setting name=“defaultExecutorType” value=“SIMPLE”/> 把value值设置为默认执行器

//如果defaultExecutorType,未设置取"SIMPLE"
String value = props.getProperty("defaultExecutorType", "SIMPLE"));
//取得枚举实例
ExecutorType et = ExecutorType.valueOf(value)
configuration.setDefaultExecutorType(et);

configuration.newExecutor(tx, execType);取得一个执行器,我们点进去看看他的逻辑

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  	//默认取SIMPLE
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    //根据配置的执行器类型不同,new不同的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);
    }
    //加载插件
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

如果需要缓存功能,需要CachingExecutor来装饰基本执行器

public class CachingExecutor implements Executor {
  //真正执行数据库操作的executor
  private Executor delegate;
  //管理缓存的TransactionalCacheManager
  private TransactionalCacheManager tcm = new TransactionalCacheManager();

  public void close(boolean forceRollback) {
    try {
      //issues #499, #524 and #573
      if (forceRollback) { 
        tcm.rollback();
      } else {
        tcm.commit();
      }
    } finally {
      delegate.close(forceRollback);
    }
  }

	//接受一个目标执行器
  public CachingExecutor(Executor delegate) {
    this.delegate = delegate;
    delegate.setExecutorWrapper(this);
  }
	//执行更新操作会刷新缓存
  public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    flushCacheIfRequired(ms); //刷新缓存
    return delegate.update(ms, parameterObject);
  }


  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
	//接口所映射的xml是否开启二级缓存
	//MyBatis在解析mapper.xml的时候会将cache标签解析为对应的Cache对象,用作二级缓存
    Cache cache = ms.getCache();
    if (cache != null) {
    	//是否刷新缓存看<select>节点的配置
      flushCacheIfRequired(ms);
      //节点是否开启二级缓存,查询默认为true,update 默认false,是否配置了resultHandler
      if (ms.isUseCache() && resultHandler == null) {
    	//如果sql中调用的是存储过程,验证其规则,不允许out类型数据开启缓存
        ensureNoOutParams(ms, parameterObject, boundSql);
        //从缓存中取出结果集
        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); // issue #578. Query must be not synchronized to prevent deadlocks
        }
        return list;
      }
    }
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
  
  private void ensureNoOutParams(MappedStatement ms, Object parameter, BoundSql boundSql) {
	//是否是存储过程
    if (ms.getStatementType() == StatementType.CALLABLE) {
    	//遍历参数
      for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
    	  //不支持用out参数缓存存储过程
        if (parameterMapping.getMode() != ParameterMode.IN) {
          throw new ExecutorException("Caching stored procedures with OUT params is not supported.  Please configure useCache=false in " + ms.getId() + " statement.");
        }
      }
    }
  }
}

如上我们可以看到update是会刷新缓存的,而我们从缓存中取出数据以及存储缓存需要Cache,CacheKey这两个实例,从TransactionalCacheManager(事务缓存管理器)中取出

public class TransactionalCacheManager {

  //key为二级缓存实例,value 事务缓存
  private Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();

  //清除事务缓存
  public void clear(Cache cache) {
    getTransactionalCache(cache).clear();
  }
  
  //取得缓存
  public Object getObject(Cache cache, CacheKey key) {
    return getTransactionalCache(cache).getObject(key);
  }
  
  public void putObject(Cache cache, CacheKey key, Object value) {
    getTransactionalCache(cache).putObject(key, value);
  }
  //所有事务缓存提交
  public void commit() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.commit();
    }
  }
	//所有事务缓存回滚
  public void rollback() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.rollback();
    }
  }

  //取得事务缓存
  private TransactionalCache getTransactionalCache(Cache cache) {
    TransactionalCache txCache = transactionalCaches.get(cache);
    if (txCache == null) {
      txCache = new TransactionalCache(cache);
      transactionalCaches.put(cache, txCache);
    }
    return txCache;
  }

}

事务缓存管理器里面持有了一个hashMap,key 为二级缓存,value 为事务缓存,TransactionalCache和二级缓存一样,实现了Cache接口,因为执行事务数据可能回滚,所以不能将数据直接存入二级缓存,可以先存在事务缓存中,commit后再放置二级缓存。我们可以看到当缓存执行器close时,事务缓存会执行commit,或者rollback 。 我们从缓存中取出数据时,调用TransactionalCache的getobject方法,先根据key 也就是二级缓存取出事务缓存,事务缓存会判断该事务是否提交或者关闭,如果该事务还在执行,则返回null,commit或者close了则会根据Cachekey从二级缓存中取出值

二级缓存是一个HashMap,是用CacheKey 作为他的键,查询的结果集做为值, 在cache中唯一确定一个缓存项需要使用缓存项的key,Mybatis中因为涉及到动态SQL等多方面因素,
其缓存项的key不能仅仅通过一个String表示,所以MyBatis 提供了CacheKey类来表示缓存项的key,在一个CacheKey对象中可以封装多个影响缓存项的因素

   //CachingExecutor 的query方法,当核心配置文件开启二级缓存时创建CacheKey
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    //创建缓存key
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
	//调用的时BaseExecutor的createCacheKey
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
  }
/**
 *		BaseExecutor: BaseExecutor是一个实现了Executor接口的抽象类,定义若干抽象方法,在执行的时候,把具体的操作委托给子类进行执行。 
 * */
public abstract class BaseExecutor implements Executor {
  /**
   * 	获取一个缓存key的实例,
   * */
  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());   //节点的id
    cacheKey.update(rowBounds.getOffset()); //分页的rows
    cacheKey.update(rowBounds.getLimit()); //分页的page
    cacheKey.update(boundSql.getSql()); //sql 
    //动态sql中所有的jdbcType,javaType,mode等信息
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    //typeHandle信息
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    for (int i = 0; i < parameterMappings.size(); i++) { // mimic DefaultParameterHandler logic
      ParameterMapping parameterMapping = parameterMappings.get(i);
      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);
      }
    }
    return cacheKey;
  }
}

可以看到,节点的id,分页的rows,page, sql,参数,动态sql中所有的jdbcType,javaType,mode等信息,都影响着cachekey的行为

从缓存中没有取得数据时,就需要执行sql来取数据了。mybatis得3种执行器 SimpleExecutor,BatchExecutor,ReuseExecutor ,他们都继承了BaseExecutor
1.BaseExecutor

/**
 *		BaseExecutor: BaseExecutor是一个实现了Executor接口的抽象类(模板),定义若干抽象方法,在执行的时候,把具体的操作委托给子类进行执行。 
 * */
public abstract class BaseExecutor implements 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");
    this.closed = false;
    this.configuration = configuration;
    this.wrapper = this;
  }

 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);
  }
	/**
	 * 	@param BoundSql : 由MappedStatement得到,BoundSql内存储了执行的sql语句和命名参数
	 * */
  @SuppressWarnings("unchecked")
  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.");//判断是否已经关闭Executor
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      //从一级缓存中取出,设置了resultHandler缓存是不会用一级缓存
      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(); //延迟加载
      }
      deferredLoads.clear(); // issue #601
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        clearLocalCache(); // issue #482
      }
    }
    return list;
  }
}

2.SimpleExecutor

/**
 *	simpleExecutor的特点 : 每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。(可以是Statement或PrepareStatement对象)
 *	simpleExecutor的主要两个功能doQuery和doUpdate insert,delete 也都是update
 */

public class SimpleExecutor extends BaseExecutor {
/**
 * 	执行修改数据库数据时调用的类
 * 	@param MappedStatement mappenStatement 就是Mapper的一个节点封装的对象
 * 	@param Object Sql的参数
 * */
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler((Executor) this, ms, parameter, RowBounds.DEFAULT, null, null);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }
}
/**
 * 	@param MappedStatement 封装了查询节点的对象,
 * 	@param parameter 参数
 * 	@param RowBounds mybatis自带的分页类
 * 	@param ResultHandler 结果集处理器
 * 	@param BoundSql 处理完任何动态内容后,用于存储实际执行的sql,参数等
 * */
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();
      //通过configuration取得RoutingStatementHandler,
      StatementHandler handler = configuration.newStatementHandler((Executor) wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //取得Statement
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

SimpleExecutor基本就是直接调用预编译对象来执行sql了

3.BatchExecutor

/**
 * batch模式重复使用已经预处理的语句,并且批量执行所有更新语句,显然batch性能将更优,
 * 但batch模式也有自己的问题,比如在Insert操作时,在事务没有提交之前,是没有办法获取到自增的id
 * 设置ExecutorType.BATCH原理:把SQL语句发个数据库,数据库预编译好,数据库等待需要运行的参数,接收到参数后
 *	一次运行,ExecutorType.BATCH只打印一次SQL语句,多次设置参数步骤,
 */
public class BatchExecutor extends BaseExecutor {
 /**
  * 当同一个Sqlsession实例执行update(包含了insert,delete)时,如果执行的是同一个语句,将执行批处理操作,
  * 如果语句有A,B 先执行A 10次,再执行B 10次,最后再执行A 10次,那么将执行3次批处理操作,因为只从尾部取出上一次的statement对象
  */
public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
	//取得Configuration
    final Configuration configuration = ms.getConfiguration();
    //取得StatementHandler
    final StatementHandler handler = configuration.newStatementHandler((Executor) this, ms, parameterObject, RowBounds.DEFAULT, null, null);
    final BoundSql boundSql = handler.getBoundSql();
    //取得Sql
    final String sql = boundSql.getSql();
    final Statement stmt;
    //如果sql是上次执行得sql,mapperStatement是上次得mapperStatement
    if (sql.equals(currentSql) && ms.equals(currentStatement)) {
      int last = statementList.size() - 1;
      //取得最后Statement对象 
      stmt = statementList.get(last);
      //参数放在最后的batchResult实例上
      BatchResult batchResult = batchResultList.get(last);
      batchResult.addParameterObject(parameterObject);
    } else {
      //如果不存在就创建一个批处理操作
      Connection connection = getConnection(ms.getStatementLog());
      stmt = handler.prepare(connection);
      currentSql = sql;
      currentStatement = ms;
      //添加批量处理操作
      statementList.add(stmt);
      batchResultList.add(new BatchResult(ms, sql, parameterObject));
    }
    handler.parameterize(stmt);
    //调用jdbc的批处理操作
    handler.batch(stmt);
    return BATCH_UPDATE_RETURN_VALUE;
  }

  public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException {
    Statement stmt = null;
    try {
      //执行query将刷新批处理,清除存储批处理的容器,
      flushStatements();
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler((Executor) wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);
      Connection connection = getConnection(ms.getStatementLog());
      stmt = handler.prepare(connection);
      handler.parameterize(stmt);
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
}

4,ReuseExecutor

/**
 * 把sql作为key,statement为value,存入hashMap中,如果sql相同,从map中取得statement
 */
public class ReuseExecutor extends BaseExecutor {
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler((Executor) this, ms, parameter, RowBounds.DEFAULT, null, null);
    //取得Statement
    Statement stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.update(stmt);
  }

  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler((Executor) wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    //取得Statement
    Statement stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.<E>query(stmt, resultHandler);
  }
  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    BoundSql boundSql = handler.getBoundSql();
    String sql = boundSql.getSql();
    //如果有相同sql,从容器中取出statement
    if (hasStatementFor(sql)) {
      stmt = getStatement(sql);
    } else {
    	//否则,取得新的statement,放入map中
      Connection connection = getConnection(statementLog);
      stmt = handler.prepare(connection);
      putStatement(sql, stmt);
    }
    handler.parameterize(stmt);
    return stmt;
  }
}

总结 : CacheExecutor 是一个装饰器,使被装饰的类具有二级缓存的功能,当mybatis读取核心配置文件将mapper接口和xml封装为MapperStatement 时就为每个mapperStatement构建了对应的cache,所以二级缓存时是对应mapperStatement,但CacheExecutor里的TransactionalCacheManager是可以清除或者添加数据进二级缓存,在执行update时,会清除所有的缓存,而不是更具cachekey来清除固定的value 。
BaseExecutor :是一个模板类 封装了一级缓存和延迟加载的功能,但查询功能却委托给了不同的子类。一级缓存好像有些鸡肋,谁会没事在同一个sqlSession里面查询两次相同的sql并且参数相同啊,但他默认开启,如果二级缓存开启的话,取得的值将会放在二级缓存当中。延迟加载是一个很复杂的功能,我们以后再谈论。
BatchExecutor : 批处理的执行器,在执行update时(当然包括了insert,delete)会将创建的编译对象存储在map中,每次有新的update语句进来时会取map得最后一个key来判断是否时相同得sql,mapperStatement,如果相同就加入批处理之中。这对于大批量得对一个表里插入数据来说能很明显得提升性能。但在执行query时,并不会有性能得提升,却会刷新update中得map
ReuseExecutor : 重用执行器,创建了编译对象就存入map中,如果下次执行相同得sql,就从缓存中取出编译对象,如果执行相同得sql,参数不同,ReuseExecutor 还是有点用得
SimpleExecutor : 每次都创建新得编译对象,没什么好说的

[上一篇]: Mybais 源码分析二 SqlSession

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用中提到,Mybatis是一个可以使用简单的XML或者注解来配置和映射原生信息的框架,它可以将接口和Java的POJO映射成数据库中的记录。同时,Mybatis支持定制化SQL、存储过程以及高级映射,避免了几乎所有的JDBC代码和手动设置参数以及获取结果集的繁琐操作。 要进行Mybatis的分析,需要深入研究Mybatis的核心组件和原理。其中,SqlSessionFactoryBuilder用于构建SqlSessionFactory,SqlSessionFactory负责创建SqlSession,SqlSession是与数据库交互的主要接口,通过SqlSession可以执行SQL语句并获取结果。在SqlSession的底层,涉及到Executor、StatementHandler、ParameterHandler和ResultSetHandler等核心组件。 Executor负责执行SQL语句,StatementHandler负责处理SQL语句的预编译和参数设置,ParameterHandler负责处理SQL语句的参数传递,ResultSetHandler负责处理SQL语句的结果集。Mybatis通过这些核心组件的协作来完成数据库操作。 在具体操作时,Mybatis的SQL映射文件(或注解)中定义了SQL语句和参数映射关系,Mybatis会根据配置的Mapper接口和对应的SQL语句,动态生成Mapper接口的实现类。通过动态代理的方式,实现了Mapper接口的方法与SQL语句的绑定,使得开发者可以直接调用Mapper接口的方法来执行SQL语句。 总之,Mybatis的分析需要深入了解其核心组件和原理,包括SqlSessionFactory、SqlSession、Executor、StatementHandler、ParameterHandler和ResultSetHandler等。通过分析这些组件的工作原理和协作关系,可以更好地理解Mybatis的内部实现机制。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Mybatis的分析](https://blog.csdn.net/zyyforever/article/details/101289858)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值