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

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 <E>
     * @return
     * @throws SQLException
     */
    <E> List<E> 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 <E>
     * @return
     * @throws SQLException
     */
    <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

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

    /**
     * 批量执行SQL语句
     * @return
     * @throws SQLException
     */
    List<BatchResult> 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();
        }
    }

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值