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添加到CacheKeycacheKey.update(ms.getId());// 将offset添加到CacheKeycacheKey.update(rowBounds.getOffset());// 将limit添加到CacheKeycacheKey.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);}// 将实参参数值添加到CacheKeycacheKey.update(value);}}// 环境不为空if (configuration.getEnvironment() != null) {// 将当前环境添加到CacheKeycacheKey.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节点配置了flushCacheif (queryStack == 0 && ms.isFlushCacheRequired()) {// 先清空缓存clearLocalCache();}List<E> list;try {// 查询层数+1queryStack++;// 先查询 一级缓存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();}// 加载完成后清除deferredLoadsdeferredLoads.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();// 处理缓存的SQLflushStatements();if (required) {// 提交事务transaction.commit();}
}

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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/536964.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

接收xml参数_SpringBoot实战(二):接收xml请求

强烈推荐一个大神的人工智能的教程&#xff1a;http://www.captainbed.net/zhanghan【前言】最近在对接一个第三方系统&#xff0c;需要接收第三方系统的回调&#xff0c;而且格式为XML形式&#xff0c;之前自己一般接收的参数是Json形式&#xff0c;于是乎做个实验验证一下使用…

报错 插入更新_window如何解决mysql数据量过大导致的报错

window如何解决报错“The total number of locks exceeds the lock table size”第一大步&#xff0c;查看mysql配置信息在CMD中输入mysql -hlocalhost -uroot -p #如果设置了密码直接接在p 后面 show variables like %storage_engine%以下为结果可以看到InnoDB是MySQL的默认引…

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

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

Mybatis源码阅读(五 ):接口层——SqlSession

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

插入公式_一个小工具,彻底帮你搞定在Markdown中插入公式的问题

在编辑Markdown文档时&#xff0c;插入公式是一个挺麻烦的活儿。需要掌握LaTex语法。我自己看完语法后&#xff0c;直接放弃&#xff0c;这绝对是反人类的语法。&#xff08;好吧&#xff0c;是我不会用...&#xff09;但是&#xff0c;我相信你看了这篇文章后&#xff0c;绝对…

Mybatis源码阅读(一):Mybatis初始化1.2 —— 解析别名、插件、对象工厂、反射工具箱、环境

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

Google 修改 Chrome API,防止隐身模式检测

开发四年只会写业务代码&#xff0c;分布式高并发都不会还做程序员&#xff1f; 在使用 Chrome 浏览网页时&#xff0c;某些网站会使用某种方法来确定访问者是否处于隐身模式&#xff0c;这是一种隐私泄漏行为。Google 目前正在考虑修改 Chrome 的相关 API&#xff0c;来杜绝…

Mybatis源码阅读(一):Mybatis初始化1.1 解析properties、settings

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

JavaScript异步基础

唯一比不知道代码为什么崩溃更可怕的事情是&#xff0c;不知道为什么一开始它是工作的&#xff01;在 ECMA 规范的最近几次版本里不断有新成员加入&#xff0c;尤其在处理异步的问题上&#xff0c;更是不断推陈出新。然而&#xff0c;我们在享受便利的同时&#xff0c;也应该了…

Flutter、ReactNative、uniapp对比

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

多线程中ThreadLocal的使用

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

注解版poi操作工具

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

Kali Linux 2019.1 发布,Metasploit 更新到 5.0 版本

百度智能云 云生态狂欢季 热门云产品1折起>>> Kali Linux 2019.1 发布了&#xff0c;Kali 前身 BackTrack&#xff0c;它是一个基于 Debian 的 Linux 发行版&#xff0c;主要用于信息安全行业&#xff0c;其包含了一系列安全、渗透测试和取证工具。此版本 Linux 内核…

peewee mysql_scrapy中利用peewee插入Mysql

前两天老大布置一个任务&#xff0c;说爬下来的数据要存入数据库中&#xff0c;丢给我一个peewee&#xff0c;说用这个。当时的我两眼一抹黑&#xff0c;这是个什么东西呀&#xff0c;我知道scrapy的数据存入数据库是在pipelines中进行设置但是peewee是什么东西呢。经过两天不懈…

Java版数据结构与算法——线性表

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

基于 CODING 的 Spring Boot 持续集成项目

本文作者&#xff1a;CODING 用户 - 廖石荣 持续集成的概念 持续集成(Continuous integration,简称 CI&#xff09;是一种软件开发实践&#xff0c;即团队开发成员经常集成他们的工作&#xff0c;通常每个成员每天至少集成一次&#xff0c;也就意味着每天可能会发生多次集成。每…

Mybatis组成部分

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

一年java工作经验-面试总结

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

linux mysql python包_03_mysql-python模块, linux环境下python2,python3的

---恢复内容开始---1、Python2 正常[rootIP ~]#pip install mysql-pythonDEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 wont be maintained after that date. A future version of pip will drop …

两年Java工作经验应该会些什么技术

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…