专栏首页波波烤鸭Mybatis源码分析之-Executor

Mybatis源码分析之-Executor

  mybatis的源代码相对于spring的来说简单了很多,对于初学者,可以先了解了mybatis的源码后再去了解spring的源码,本文主要来分析下Executor的内容

Executor介绍

  Executor是mybatis的一个核心接口,所有的Mapper语句的执行都是通过Executor进行的。类结构图如下

1.Executor(顶层接口)

  父接口,在此接口中定义了各种处理方法。具体如下:

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;
  // 刷新statements
  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 对象
  Transaction getTransaction();
	// 关闭
  void close(boolean forceRollback);
	// 是否关闭
  boolean isClosed();
	// 设置执行器增强器
  void setExecutorWrapper(Executor executor);
}

2.BaseExecutor

  BaseExecutor是一个抽象类,采用模板方法的设计模式。它实现了Executor接口,实现了执行器的基本功能。具体使用哪一个Executor则是可以在 mybatis 的 config.xml 中进行配置的。默认为SimpleExecutor;

<settings>
    <!--SIMPLE、REUSE、BATCH-->
    <setting name="defaultExecutorType" value="SIMPLE"/>
</settings>

2.1构造方法

  子类的构造方法会调用 BaseExecutor 的构造方法。默认都支持一级缓存;

  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;
  }

2.2 update方法

  BaseExecutor中实现了update方法,改方法执行DML操作的时候都会执行,通过源码我们会发现,执行DML操作前会清空一级缓存

  @Override
  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();
    // 调用doUpdate方法执行
    return doUpdate(ms, parameter);
  }

2.3 query方法

  查询操作会先在缓存中查询,缓存命中失败后再去数据中查询。

2.4 createCacheKey

  查看缓存key的生成。了解cacheKey的组成。一级缓存通过 HashMap 实现,它的键对象根据SQL的ID,参数,SQL本身,分页参数以及JDBC的参数信息构成。

@Override
  // 创建CacheKey对象
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    CacheKey cacheKey = new CacheKey();
    // MappedStatement的id
    cacheKey.update(ms.getId());
    // 分页参数的offset
    cacheKey.update(rowBounds.getOffset());
    // 分页参数的limit
    cacheKey.update(rowBounds.getLimit());
    // SQL语句本身
    cacheKey.update(boundSql.getSql());
    // 传递给jdbc的参数
    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;
  }

2.5 定义的抽象方法

  定义的抽象方法有如下几个,交给实现类来实现。

// 定义的四个抽象方法,在去掉 do 前缀的相应方法中被调用
  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;

BaseExecutor的实现类是在Configuration中创建出来的

  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);
    }
    // 如果有拦截器 则会返回对应的代理类
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

3.SimpleExecutor

  最简单的执行器,根据对应的sql直接执行即可,不会做一些额外的操作;拼接完SQL之后,直接交给 StatementHandler去执行。   每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。(可以是Statement或PrepareStatement对象) 我们选择doUpdate()方法来看下

 @Override
 public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
   Statement stmt = null;
   try {
     Configuration configuration = ms.getConfiguration();
     // 获取Statement处理器对象
     StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
     // 获取具体的Statement对象
     stmt = prepareStatement(handler, ms.getStatementLog());
     // 处理器执行更新操作
     return handler.update(stmt);
   } finally {
     closeStatement(stmt);
   }
 }

prepareStatement方法

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    // 获取连接通道
    Connection connection = getConnection(statementLog);
    // 创建Statement对象
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }

prepare方法

所以BaseExecutor中具体的Statement可以是这三种情况。

4.BatchExecutor

  执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理的;可以是Statement或PrepareStatement对象.

5.ReuseExecutor

  可重用的执行器,重用的对象是Statement,也就是说该执行器会缓存同一个sql的Statement,省去Statement的重新创建,优化性能。内部的实现是通过一个HashMap来维护Statement对象的。由于当前Map只在该session中有效,所以使用完成后记得调用flushStatements来清除Map。

public class ReuseExecutor extends BaseExecutor {


  private final Map<String, Statement> statementMap = new HashMap<String, Statement>();
  
  // 调用父类构造器
  public ReuseExecutor(Configuration configuration, Transaction transaction) {
    super(configuration, transaction);
  }

  
  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    BoundSql boundSql = handler.getBoundSql();
    String sql = boundSql.getSql();
    if (hasStatementFor(sql)) {
        // 如果缓存了该SQL,则返回其Statement对象
      stmt = getStatement(sql);
      applyTransactionTimeout(stmt);
    } else {
        // 如果没有缓存该SQL,则创建SQL的Statement,并加入缓存
      Connection connection = getConnection(statementLog);
      stmt = handler.prepare(connection, transaction.getTimeout());
      putStatement(sql, stmt);
    }
    handler.parameterize(stmt);
    return stmt;
  }

  // 是否缓存了这个 sql
  private boolean hasStatementFor(String sql) {
    try {
      return statementMap.keySet().contains(sql) && !statementMap.get(sql).getConnection().isClosed();
    } catch (SQLException e) {
      return false;
    }
  }
  
  // 返回指定sql的 Statement
  private Statement getStatement(String s) {
    return statementMap.get(s);
  }

  // 添加SQL和Statement
  private void putStatement(String sql, Statement stmt) {
    statementMap.put(sql, stmt);
  }

}

6.CachingExecutor

  启用于二级缓存时的执行器;采用静态代理;代理一个 Executor 对象。执行 update 方法前判断是否清空二级缓存;执行 query 方法前先在二级缓存中查询,命中失败再通过被代理类查询。

public class CachingExecutor implements Executor {
    // 持有的 Executor,最终的操作都由该对象实现
    private final Executor delegate;
    private final TransactionalCacheManager tcm = new TransactionalCacheManager();

    public CachingExecutor(Executor delegate) {
        this.delegate = delegate;
        delegate.setExecutorWrapper(this);
    }
    
    public int update(MappedStatement ms, Object parameterObject) throws SQLException {
        this.flushCacheIfRequired(ms);
        return this.delegate.update(ms, parameterObject);
    }
    
    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) {
            this.flushCacheIfRequired(ms);
            if (ms.isUseCache() && resultHandler == null) {
                this.ensureNoOutParams(ms, boundSql);
                List<E> list = (List)this.tcm.getObject(cache, key);
                if (list == null) {
                    list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                    this.tcm.putObject(cache, key, list);
                }

                return list;
            }
        }

        return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }
        // 是否清空二级缓存
        private void flushCacheIfRequired(MappedStatement ms) {
        Cache cache = ms.getCache();
        if (cache != null && ms.isFlushCacheRequired()) {
            this.tcm.clear(cache);
        }

    }
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Salesforce连接器在Yelp中的应用案例

    Yelp是美国著名商户点评网站,创立于2004年,囊括各地餐馆、购物中心、酒店、旅游等领域的商户,用户可以在Yelp网站中给商户打分,提交评论,交流购物体验等。

    臭豆腐
  • 使用 Force.com IDE 搜索 Salesforce 元数据

    我敢肯定你一定经常问“这个字段是用在什么地方?”这样的问题。如果字段是在页面布局中或者对用户可见的话,就很容易确定字段的用途,但如果字段被用在工作流、报表或者...

    臭豆腐
  • Mongodb后台daemon方式启动(一直运行)

    有时我们使用ssh装上Mongodb之后如果这样启动 ./mongod --dbpath=/export/nerdserv...

    用户5166556
  • 如何把.csv文件导入到mysql中以及如何使用mysql 脚本中的load data快速导入

     1, 其中csv文件就相当于excel中的另一种保存形式,其中在插入的时候是和数据库中的表相对应的,这里面的colunm 就相当于数据库中的一列,对应csv表...

    用户5166556
  • ssdb 主从同步复制配置详细步骤

    有时我们在使用数据库时,像mongodb,redis和一些关系行数据,为了使数据更加安全,作为备份使用我们经常习惯使用主从复制架构,当主机上的数据出现问题时...

    用户5166556
  • Salesforce Data.com介绍

    Data.com是一个多来源的数据库,每天被用户自己编辑和更新。现在已经有3千万个联系人。每个业务联系人都有完整的属性信息:全名,职位,公司名称,邮寄地址,手机...

    臭豆腐
  • Mongodb底层java驱动框架工具类使用

    使用MongoDB需要对文档结构进行合理的设计,以满足某些特定需求。比如随机选取文档,使用skip跳过随机个文档就没有在文档...

    用户5166556
  • 此代码募集最优秀的答案

    这次的 [ 一分钟系列 ] 灰常的简单,只有短短几十来个字,考验你 Java 基础的时候到了,烧动吧,大脑!话不多说,本次代码只为募集到最优秀的答案,代码如下:

    孤独键客
  • lucene给文本索引和搜索功能的应用

    最近一段时间由于公司需要 ,模糊搜索出相似的关键词,所以直接考虑使用了lucene。

    用户5166556
  • linux下 mongodb安装和启动过程

    一  转到目录解压缩下载的压缩包 lamp@QA-clg server$ tar -zxvf mongodb.tgz 二 安装后查看: [la...

    用户5166556

扫码关注云+社区

领取腾讯云代金券