Mybatis源码阅读(四):核心接口4.2——Executor(上)

*************************************优雅的分割线 **********************************

分享一波:程序员赚外快-必看的巅峰干货

如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程

请关注微信公众号:HB荷包
在这里插入图片描述
一个能让你学习技术和赚钱方法的公众号,持续更新
*************************************优雅的分割线 **********************************
Executor

Executor是Mybatis的核心接口之一,其中定义了数据库操作的基本方法。在实际应用中涉及的SqlSession的操作都是基于Executor实现的。Executor代码如下。

/**

  • Mybatis的核心接口,定义了操作数据库的方法

  • SqlSession接口的功能都是基于Executor实现的

  • @author Clinton Begin
    */
    public interface Executor {

    ResultHandler NO_RESULT_HANDLER = null;

    /**

    • 执行update、insert、delete语句
    • @param ms
    • @param parameter
    • @return
    • @throws SQLException
      */
      int update(MappedStatement ms, Object parameter) throws SQLException;

    /**

    • 执行select
    • @param ms
    • @param parameter
    • @param rowBounds
    • @param resultHandler
    • @param cacheKey
    • @param boundSql
    • @param
    • @return
    • @throws SQLException
      */
      List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;

    /**

    • 执行select
    • @param ms
    • @param parameter
    • @param rowBounds
    • @param resultHandler
    • @param
    • @return
    • @throws SQLException
      */
      List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

    /**

    • 执行select,返回游标
    • @param ms
    • @param parameter
    • @param rowBounds
    • @param
    • @return
    • @throws SQLException
      */
      Cursor queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;

    /**

    • 批量执行SQL语句
    • @return
    • @throws SQLException
      */
      List flushStatements() throws SQLException;

    /**

    • 提交事务
    • @param required
    • @throws SQLException
      */
      void commit(boolean required) throws SQLException;

    /**

    • 回滚事务
    • @param required
    • @throws SQLException
      */
      void rollback(boolean required) throws SQLException;

    /**

    • 创建缓存中的CacheKey对象
    • @param ms
    • @param parameterObject
    • @param rowBounds
    • @param boundSql
    • @return
      */
      CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);

    /**

    • 根据CacheKey查找缓存是否出在
    • @param ms
    • @param key
    • @return
      */
      boolean isCached(MappedStatement ms, CacheKey key);

    /**

    • 清除一级缓存
      */
      void clearLocalCache();

    /**

    • 延迟加载一级缓存中的数据
    • @param ms
    • @param resultObject
    • @param property
    • @param key
    • @param targetType
      */
      void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);

    /**

    • 获取事务对象
    • @return
      */
      Transaction getTransaction();

    /**

    • 关闭Executor对象
    • @param forceRollback
      */
      void close(boolean forceRollback);

    /**

    • 检测Executor是否关闭
    • @return
      */
      boolean isClosed();

    /**

    • 设置包装的Executor
    • @param executor
      */
      void setExecutorWrapper(Executor executor);

}

[点击并拖拽以移动]

Executor接口的实现中使用到了装饰器模式和模板方法模式,关于设计模式的内容可以查看我之前的文章,这里就不贴出文章链接了。Executor的实现如图所示。

BaseExecutor

BaseExecutor是个抽象类,实现了Executor大部分的方法。BaseExecutor中主要提供了缓存管理和事务管理的基本功能,继承BaseExecutor的子类只需要实现四个基本的方法来完成数据库的相关操作即可,分别是doUpdate、doQuery、doQueryCursor、doFlushStatement。其余的方法在BaseExecutor中都有了实现。BaseExecutor的字段如下

/**
 * 事务对象
 */
protected Transaction transaction;

/**
 * 封装的Executor对象
 */
protected Executor wrapper;

/**
 * 延迟加载队列
 */
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;

/**
 * 一级缓存,用于缓存该Executor对象查询结果集映射得到的结果对象
 */
protected PerpetualCache localCache;

/**
 * 一级缓存,用来缓存输出类型的参数
 */
protected PerpetualCache localOutputParameterCache;
protected Configuration configuration;

/**
 * 记录嵌套查询的层数
 */
protected int queryStack;
/**
 * 标识Executor是否关闭
 */
private boolean closed;

一级缓存

常见的系统中,数据库资源是比较珍贵的,在web系统中的性能瓶颈主要也就是数据库。在设计系统时,会使用多种优化手段去减少数据库的直接访问,比如使用缓存。使用缓存可以减少系统与数据库的网络交互、减少数据库访问次数、降低数据库负担、降低重复创建和销毁对象等一系列的开销,从而提升系统的性能。同时,当数据库意外宕机时,缓存中保存的数据可以继续支持系统部分功能的正常展示,提高系统的可用性。Mybatis提供了一级缓存和二级缓存,我们这里先讨论一级缓存。

一级缓存是会话级别的缓存,在Mybatis中每创建一个SqlSession对象,就表示开启一次数据库会话。在一次会话中,系统可能回反复的执行相同的查询语句,如果不对数据库进行缓存,那么短时间内执行多次完全相同的SQL语句,查询到的结果集也可能完全相同,就造成了数据库资源的浪费。

为了避免这种问题,Executor对象中会建立一个简单的缓存,也就是一级缓存。它会将每次查询结果缓存起来,再执行查询操作时,会先查询一级缓存,如果存在完全一样的查询语句,则直接从一级缓存中取出相应的结果对象返回给用户,从而减少数据库压力。

一级缓存的生命周期与SqlSession相同,也就与SqlSession封装的Executor对象的生命周期相同,当调用了Executor的close方法时,该Executor中的一级缓存将会不可用。同时,一级缓存中对象的存活时间也会受其他因素影响,比如在执行update方法时,也会先清空一级缓存。
query

BaseExecutor方法会首先创建CacheKey对象,并根据CacheKey对象查找一级缓存,如果缓存命中则直接返回缓存中记录的结果对象。如果没有命中则查询数据库得到结果集,之后将结果集映射成对象保存到一级缓存中,同时返回结果对象。query方法如下所示。

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 获取BoundSql对象
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 创建CacheKey对象
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

在query方法中会先获取到boundSql对象,并且去创建CacheKey对象,再调用query的一个重载方法。

这里的CacheKey由MappedStatement的id、对应的offset和limit、包含问号的sql语句、用户传递的实参、Environment的id五部分构成,代码如下。

/**
 * 创建CacheKey对象
 * CacheKey由Sql节点的id、offset、limit、sql、实参、环境组成
 *
 * @param ms
 * @param parameterObject
 * @param rowBounds
 * @param boundSql
 * @return
 */
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) {
        throw new ExecutorException("Executor was closed.");
    }
    CacheKey cacheKey = new CacheKey();
    // 将sql节点的id添加到CacheKey
    cacheKey.update(ms.getId());
    // 将offset添加到CacheKey
    cacheKey.update(rowBounds.getOffset());
    // 将limit添加到CacheKey
    cacheKey.update(rowBounds.getLimit());
    // 将SQL添加到CacheKey(包含?的sql)
    cacheKey.update(boundSql.getSql());
    // 获取参数映射
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    // 获取类型处理器
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // 遍历参数映射
    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
            cacheKey.update(value);
        }
    }
    // 环境不为空
    if (configuration.getEnvironment() != null) {
        // 将当前环境添加到CacheKey
        cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
}

而query的重载方法会根据创建的CacheKey对象查询一级缓存。如果缓存命中则将缓存中记录的结果对象返回,如果未命中,则调用doQuery方法查询数据库,并存到一级缓存。代码如下。

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.");
    }
    // 非嵌套查询并且当前select节点配置了flushCache
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
        // 先清空缓存
        clearLocalCache();
    }
    List<E> list;
    try {
        // 查询层数+1
        queryStack++;
        // 先查询 一级缓存
        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) {
        // 触发DeferredLoad加载一级缓存中记录的嵌套查询的结果对象
        for (DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
        }
        // 加载完成后清除deferredLoads
        deferredLoads.clear();
        if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            // 根据localCacheScope配置决定是否清空一级缓存
            clearLocalCache();
        }
    }
    return list;
}

BaseExecutor中缓存除了缓存结果集以外,在分析嵌套查询时,如果一级缓存中缓存了嵌套查询的结果对象,则可以从一级缓存中直接加载该结果对象。如果一级缓存中记录的嵌套查询的结果对象并未完全加载,则可以通过DeferredLoad实现类实现延迟加载的功能。与这个流程相关的方法有两个,isCached方法负责检测是否缓存了指定查询的结果对象,deferLoad方法负责创建DeferredLoad对象并添加到deferredLoad集合中。代码如下。

/**
 * 检测是否缓存了指定查询的结果对象
 *
 * @param ms
 * @param key
 * @return
 */
@Override
public boolean isCached(MappedStatement ms, CacheKey key) {
    // 检测缓存中是否花奴才能了CacheKey对象
    return localCache.getObject(key) != null;
}

/**
 * 负责创建DeferredLoad对象并将其添加到deferredLoads集合中
 *
 * @param ms
 * @param resultObject
 * @param property
 * @param key
 * @param targetType
 */
@Override
public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
    if (closed) {
        throw new ExecutorException("Executor was closed.");
    }
    DeferredLoad deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType);
    if (deferredLoad.canLoad()) {
        // 一级缓存中已经记录了指定查询结果的对象,直接从缓存中加载对象,并设置到外层对象
        deferredLoad.load();
    } else {
        // 将deferredLoad对象添加到deferredLoads队列中,待整个外层查询结束后再加载结果对象
        deferredLoads.add(new DeferredLoad(resultObject, property, key, localCache, configuration, targetType));
    }
}

DeferredLoad是定义在BaseExecutor中的内部类,它负责从loadCache缓存中延迟加载结果对象,含义如下。

    /**
     * 外层对象对应的MetaObject
     */
    private final MetaObject resultObject;
    /**
     * 延迟加载的属性名称
     */
    private final String property;
    /**
     * 延迟加载的属性类型
     */
    private final Class<?> targetType;
    /**
     * 延迟加载的结果对象在一级缓存中的CacheKey
     */
    private final CacheKey key;
    /**
     * 一级缓存
     */
    private final PerpetualCache localCache;
    private final ObjectFactory objectFactory;
    /**
     * 负责结果对象的类型转换
     */
    private final ResultExtractor resultExtractor;

DeferredLoad的canLoad方法负责检测缓存项是否已经完全加载到缓存中。BaseExecutor的queryFromDatabase方法中,开始调用doQuery查询数据库之前,会先在localCache中放一个占位符,待查询完毕后会将key替换成真实的数据,此时缓存就完全加载了。queryFromDatabase方法的实现如下。

/**
 * 从数据库中查询
 *
 * @param ms
 * @param parameter
 * @param rowBounds
 * @param resultHandler
 * @param key
 * @param boundSql
 * @param <E>
 * @return
 * @throws SQLException
 */
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 {
        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;
}

canLoad和load方法实现如下。

    /**
     * 判断是否是完全加载
     *
     * @return
     */
    public boolean canLoad() {
        return localCache.getObject(key) != null && localCache.getObject(key) != EXECUTION_PLACEHOLDER;
    }

    /**
     * 负责从缓存中加载结果对象,设置到外层对象 的属性中
     */
    @SuppressWarnings("unchecked")
    public void load() {
        // 从缓存中查询指定的结果对象
        List<Object> list = (List<Object>) localCache.getObject(key);
        // 将缓存的结果对象转换成指定的类型
        Object value = resultExtractor.extractObjectFromList(list, targetType);
        // 设置到外层对象的对应属性
        resultObject.setValue(property, value);
    }

clearLocalCache方法用于清空缓存。query方法会根据flushCache属性和localCacheScope配置决定是否清空一级缓存。update方法在执行insert、update、delete三类SQL语句之前,会清空缓存。代码比较简单这里就不贴了。
事务操作

在BatchExecutor中可以缓存多条SQL,等待合适的时机将缓存的多条SQL一起发送给数据库执行。Executor.flushStatements方法主要是针对批处理多条SQL语句的,会调用doFlushStatements方法处理Executor中缓存的多条SQL语句,在BaseExecutor的commit、rollback方法中会首先调用flushStatement方法,再执行相关事务操作,方法具体的实现如下。

public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
    if (closed) {
        throw new ExecutorException("Executor was closed.");
    }
    return doFlushStatements(isRollBack);
}

BaseExecutor.commit方法首先会清空一级缓存,调用flushStatements,最后才根据参数决定是否真正提交事务。代码如下,

/**
 * 提交事务
 * @param required
 * @throws SQLException
 */
@Override
public void commit(boolean required) throws SQLException {
    if (closed) {
        throw new ExecutorException("Cannot commit, transaction is already closed");
    }
    // 清除缓存
    clearLocalCache();
    // 处理缓存的SQL
    flushStatements();
    if (required) {
        // 提交事务
        transaction.commit();
    }
}

*************************************优雅的分割线 **********************************

分享一波:程序员赚外快-必看的巅峰干货

如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程

请关注微信公众号:HB荷包
在这里插入图片描述
一个能让你学习技术和赚钱方法的公众号,持续更新
*************************************优雅的分割线 **********************************

posted @ 2020-12-19 17:20  游在空中的鱼  阅读(101)  评论(0编辑  收藏  举报