【MyIbatis】MyBatis 核心配置综述之Executor

2023-10-29

在这里插入图片描述

1.概述

转载:MyBatis 核心配置综述之Executor

上一篇我们对SqlSession和SqlSessionFactory的创建过程有了一个详细的了解,但上述的创建过程只是为SQL执行和SQL映射做了基础的铺垫而已,就和我们Spring源码为Bean容器的加载进行许多初始化的工作相同,那么做好前期的准备工作接下来该做什么了?该做数据库连接驱动管理和SQL解析工作了!那么本篇本章就来讨论一下数据库驱动连接管理和SQL解析的管理组件之 Executor执行器。

2.MyBatis四大组件之 Executor执行器

每一个SqlSession都会拥有一个Executor对象,这个对象负责增删改查的具体操作,我们可以简单的将它理解为JDBC中Statement的封装版。

Executor的继承结构

在这里插入图片描述
如图所示,位于继承体系最顶层的是Executor执行器,它有两个实现类,分别是BaseExecutorCachingExecutor

BaseExecutor 是一个抽象类,这种通过抽象的实现接口的方式是适配器设计模式接口适配的体现,是Executor的默认实现,实现了大部分Executor接口定义的功能,降低了接口实现的难度。BaseExecutor的子类有三个,分别是SimpleExecutor、ReuseExecutor和BatchExecutor

SimpleExecutor: 简单执行器,是MyBatis中默认使用的执行器,每执行一次update或select,就开启一个Statement对象,用完就直接关闭Statement对象(可以是Statement或者是PreparedStatment对象)

ReuseExecutor: 可重用执行器,这里的重用指的是重复使用Statement,它会在内部使用一个Map把创建的Statement都缓存起来,每次执行SQL命令的时候,都会去判断是否存在基于该SQL的Statement对象,如果存在Statement对象并且对应的connection还没有关闭的情况下就继续使用之前的Statement对象,并将其缓存起来。因为每一个SqlSession都有一个新的Executor对象,所以我们缓存在ReuseExecutor上的Statement作用域是同一个SqlSession。

BatchExecutor: 批处理执行器,用于将多个SQL一次性输出到数据库

CachingExecutor: 缓存执行器,先从缓存中查询结果,如果存在,就返回;如果不存在,再委托给Executor delegate 去数据库中取,delegate可以是上面任何一个执行器

3.Executor创建过程以及源码分析

上面我们分析完SqlSessionFactory的创建过程的准备工作后,我们下面就开始分析会话的创建以及Executor的执行过程。

在创建完SqlSessionFactory之后,调用其openSession方法:

SqlSession sqlSession = factory.openSession();

SqlSessionFactory的默认实现是DefaultSqlSessionFactory,所以我们需要关心的就是DefaultSqlSessionFactory中的openSession()方法

openSession调用的是openSessionFromDataSource方法,传递执行器的类型,方法传播级别,是否自动提交,然后在openSessionFromDataSource方法中会创建一个执行器

public SqlSession openSession() {
  return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      // 得到configuration 中的environment
      final Environment environment = configuration.getEnvironment();
      // 得到configuration 中的事务工厂
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 获取执行器
      final Executor executor = configuration.newExecutor(tx, execType);
      // 返回默认的SqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

调用newExecutor方法,根据传入的ExecutorType类型来判断是哪种执行器,然后执行相应的逻辑

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    // defaultExecutorType默认是简单执行器, 如果不传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);
    }
    // 如果允许缓存,则使用缓存执行器
  	// 默认是true,如果不允许缓存的话,需要手动设置
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
  	// 插件开发。
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

ExecutorType的选择:

ExecutorType来决定Configuration对象创建何种类型的执行器,它的赋值可以通过两个地方进行赋值:

可以通过标签来设置当前工程中所有的SqlSession对象使用默认的Executor

<settings>
<!--取值范围 SIMPLE, REUSE, BATCH -->
	<setting name="defaultExecutorType" value="SIMPLE"/>
</settings>

另外一种直接通过Java对方法赋值的方式

session = factory.openSession(ExecutorType.BATCH);

ExecutorType是一个枚举,它只有三个值SIMPLE, REUSE, BATCH

创建完成Executor之后,会把Executor执行器放入一个DefaultSqlSession对象中来对四个属性进行赋值,他们分别是 configuration、executor、 dirty、autoCommit

4. Executor接口的主要方法

Executor接口的方法还是比较多的,这里我们就几个主要的方法和调用流程来做一个简单的描述

4.1 大致流程

Executor中的大部分方法的调用链其实是差不多的,下面都是深入源码分析执行过程,如果你没有时间或者暂时不想深入研究的话,给你下面的执行流程图作为参考。

在这里插入图片描述

4.2 query()方法

query方法有两种形式,一种是直接查询;一种是从缓存中查询,下面来看一下源码

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

当有一个查询请求访问的时候,首先会经过Executor的实现类CachingExecutor,先从缓存中查询SQL是否是第一次执行,

  1. 如果是第一次执行的话,那么就直接执行SQL语句,并创建缓存
  2. 如果第二次访问相同的SQL语句的话,那么就会直接从缓存中提取

CachingExecutor

	// 第一次查询,并创建缓存
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  BoundSql boundSql = ms.getBoundSql(parameterObject);
  CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
  return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

MapperStatement维护了一条<select|update|delete|insert>节点的封装,包括资源(resource),配置(configuration),SqlSource(sql源文件)等。使用Configuration的getMappedStatement方法来获取MappedStatement对象

在这里插入图片描述
BoundSql这个类包括SQL的基本信息,基本的SQL语句,参数映射,参数类型等

在这里插入图片描述
上述的query方法会调用到CachingExecutor类中的query查询缓存的方法

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) {
    // 如果需要的话刷新缓存
    flushCacheIfRequired(ms);
    if (ms.isUseCache() && resultHandler == null) {
      ensureNoOutParams(ms, boundSql);
      @SuppressWarnings("unchecked")
      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); 
      }
      return list;
    }
  }
  // 委托模式,交给SimpleExecutor等实现类去实现方法。
  return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

由delegate执行query方法,delegate即是BaseExecutor,然后由具体的执行器去真正执行query方法

注意:源码中一般以do** 开头的方法都是真正加载执行的方法

// 经过一系列的调用,会调用到下面的方法(与主流程无关,故省略)
// 以SimpleExecutor简单执行器为例
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();
    // 创建StatementHandler,解析SQL语句
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    stmt = prepareStatement(handler, ms.getStatementLog());
    // 由handler来对SQL语句执行解析工作
    return handler.<E>query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}

由上面的源码可以看出,Executor执行器所起的作用相当于是管理StatementHandler 的整个生命周期的工作,包括创建、初始化、解析、关闭。

ReuseExecutor完成的doQuery 工作:几乎和SimpleExecutor完成的工作一样,其内部不过是使用一个Map来存储每次执行的查询语句,为后面的SQL重用作准备。

BatchExecutor完成的doQuery 工作:和SimpleExecutor完成的工作一样。

4.3 update() 方法

在分析完上面的查询方法后,我们再来聊一下update()方法,update()方法不仅仅指的是**update()**方法,它是一条update链,什么意思呢?

就是*insert、update、delete在语义上其实都是更新的意思,而查询在语义上仅仅只是表示的查询,那么我们来偷窥一下update方法的执行流程,与select的主要执行流程很相似,所以一次性贴出。

// 首先在顶级接口中定义update 方法,交由子类或者抽象子类去实现

// 也是首先去缓存中查询是否具有已经执行过的相同的update语句
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
  flushCacheIfRequired(ms);
  return delegate.update(ms, parameterObject);
}

// 然后再交由BaseExecutor 执行update 方法
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);
}

// 往往do* 开头的都是真正执行解析的方法,所以doUpdate 应该就是真正要执行update链的解析方法了
// 交给具体的执行器去执行
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
  Statement stmt = null;
  try {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
    stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.update(stmt);
  } finally {
    closeStatement(stmt);
  }
}

ReuseExecutor完成的doUpdate 工作:几乎和SimpleExecutor完成的工作一样,其内部不过是使用一个Map来存储每次执行的更新语句,为后面的SQL重用作准备。

BatchExecutor完成的doUpdate 工作:和SimpleExecutor完成的工作相似,只是其内部有一个List列表来一次行的存储多个Statement,用于将多个sql语句一次性输送到数据库执行.

4.4 queryCursor()方法

我们查阅其源码的时候,在执行器的执行过程中并没有发现其与query方法有任何不同之处,但是在doQueryCursor 方法中我们可以看到它返回了一个cursor对象,网上搜索cursor的相关资料并查阅其基本结构,得出来的结论是:用于逐条读取SQL语句,应对数据量

// 查询可以返回Cursor<T>类型的数据,类似于JDBC里的ResultSet类,
// 当查询百万级的数据的时候,使用游标可以节省内存的消耗,不需要一次性取出所有数据,可以进行逐条处理或逐条取出部分批量处理。
public interface Cursor<T> extends Closeable, Iterable<T> {

    boolean isOpen();

    boolean isConsumed();
  
    int getCurrentIndex();
}

4.5 flushStatements() 方法

flushStatement()的主要执行流程和query,update 的执行流程差不多,我们这里就不再详细贴代码了,简单说一下flushStatement()的主要作用,flushStatement()主要用来释放statement,或者用于ReuseExecutor和BatchExecutor来刷新缓存

4.6 createCacheKey() 方法

createCacheKey()方法主要由BaseExecutor来执行并创建缓存,MyBatis中的缓存分为一级缓存和二级缓存,关于缓存的讨论我们将在Mybatis系列的缓存章节

4.7 Executor 中的其他方法

Executor 中还有其他方法,提交commit,回滚rollback,判断是否时候缓存isCached,关闭close,获取事务getTransaction一级清除本地缓存clearLocalCache等

5. Executor 的现实抽象

在上面的分析过程中我们了解到,Executor执行器是MyBatis中很重要的一个组件,

Executor相当于是外包的boss,它定义了甲方(SQL)需要干的活(Executor的主要方法),这个外包公司是个小公司,没多少人,每个人都需要干很多工作,

boss接到开发任务的话,一般都找项目经理(CachingExecutor),项目经理几乎不懂技术,它主要和技术leader(BaseExecutor)打交道,技术leader主要负责框架的搭建,具体的工作都会交给下面的程序员来做,

程序员的技术也有优劣,高级程序员(BatchExecutor)、中级程序员(ReuseExecutor)、初级程序员(SimpleExecutor),它们干的活也不一样。一般有新的项目需求传达到项目经理这里,项目经理先判断自己手里有没有现成的类库或者项目直接套用(Cache),有的话就直接让技术leader拿来直接套用就好,没有的话就需要搭建框架,再把框架存入本地类库中,再进行解析。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【MyIbatis】MyBatis 核心配置综述之Executor 的相关文章

随机推荐

  • golang rpcx记录一次解决 context deadline exceeded的问题

    测试同时并发一万个应用去调用另一个微服务应用结果出现了上下文切换超时的问题 默认的超时时间只有一秒时间 程序在高并发的场景下很容易触发这个错误 最简单的解决办法就是把超时时间调大一点 以前设置的是一秒 现在调成五秒 另一种解决办法就是优化自
  • FHD、4K、8K为何物

    转自 http www 4k123 com thread 145 1 1 html 近段时间 分辨率是一个很热的话题 4K与8K两个词频繁曝光于各大行业网站 8月23日 联合国旗下的国际电讯联盟通过以日本NHK电视台所建议的7680x432
  • Springboot2.0快速入门(第一章)

    目录 一 SpringBoot简介 1 1 回顾什么是Spring 1 2 Spring是如何简化Java开发的 1 3 什么是SpringBoot 二 Hello World 2 1 准备工作 2 2 创建基础项目说明 2 3 创建第一个
  • CoreML模型分析

    准备工作 首先得有一个Xcode以及一个简单的添加了CoreMLFramework的工程 下载模型 如官方推荐的MobileNetV2 将模型导入到工程中 并添加到你的编译项目中 双击打开 会看到这么一个页面 5 然后点击 就可以进入到模型
  • rust核心语法

    一 强类型语言 自动判断定义的变量类型 let a 323 不可变整形变量 let mut a 323 可变整形变量 变量声明方式 let a u64 323 不声明会被默认 二 表达式 1 可以在一个用 包括的块里编写一个较为复杂的表达式
  • react-container-query

    1 媒体查询 响应式组件 2 使用方法 1 引入 import ContainerQuery from react container query 2 规定屏幕尺寸 媒体查询 const query screen xs maxWidth 5
  • mysql强制指定查询使用的索引

    语法 select from table name force index index name where conditions 例如 mysql强制使用指定索引查询 SELECT FROM yrd pay flow FORCE INDE
  • 图解Netty之Pipeline、channel、Context之间的数据流向。

    以下所绘制图形均基于Netty4 0 28版本 一 connect outbound类型事件 当用户调用channel的connect时 会发起一个outbound类型的事件 该事件将在pipeline中传递 pipeline connec
  • PAT 乙级 1035 插入与归并 (C语言)

    题目 根据维基百科的定义 插入排序是迭代算法 逐一获得输入数据 逐步产生有序的输出序列 每步迭代中 算法从输入序列中取出一元素 将之插入有序序列中正确的位置 如此迭代直到全部元素有序 归并排序进行如下迭代操作 首先将原始序列看成 N 个只包
  • SetupSTM32CubeProgrammer安装教程

    SetupSTM32CubeProgrammer安装教程 下载地址 ST官方网站 链接 link https www st com en development tools stm32cubeprog html 环境准备 笔主是下载好就开始
  • Java 多线程共享模型之管程(上)

    主线程与守护线程 默认情况下 Java 进程需要等待所有线程都运行结束 才会结束 有一种特殊的线程叫做守护线程 只要其它非守护线程运行结束了 即使守护线程的代码没有执行完 也会强制结束 package Daemon import lombo
  • 系统分析师案例题【数据库篇】

    目录 1 规范化与反规范化 1 1 数据库设计过程 1 2 范式 1 3 反规范化 2 数据库索引 3 数据库视图 4 分区分表分库 5 分布式数据库系统 6 NoSQL 7 联邦数据库系统 8 数据库的性能优化 9 大数据 1 规范化与反
  • Java之网络编程

    文章目录 1 B S和C S架构 1 1B S架构 1 2C S架构 2 http协议 2 1http协议的特点 2 2http请求格式 2 3http响应格式 3 UDP协议 4 TCP协议 DNS域名解析 6 socket 6 1Ine
  • 问题:pycharm里面调用mysql失败

    项目场景 pycharm调用mysql的时候 连接数据库错误 排除密码 账户 语句等常规错误 问题 Could not connect server may not be running import pymysql 建立数据库连接 con
  • Dirty cow提权(脏牛)CVE-2016-5195 演示

    漏洞原理 脏牛 Linux内核的内存子系统在处理写入时复制时产生了竞争条件 指的是任务执行顺序异常 导致应用崩溃或者使得攻击者利用其漏洞进一步执行其他代码 最终攻击者可对其目标进行提权获得root权限 提权 下载EXP root kali
  • Spring Security OAuth2.0(二)-----简化模式/密码模式/客户端模式/刷新 token

    简化模式 代码示例 修改authorization server授权服务模块 新增 implicit 和修改回调地址为本次地址 修改第三方应用项目搭建新页面模拟 新建implicit jsp
  • 如何搭建个人博客网站【图/文教程】

    一 个人博客网站 大多人都用过一些社交平台上的博客 如QQ空间 新浪微博 网易博客等等 但这些大多都是面向大众平台 而且不是自己独有 自由的博客 想搭建一个属于自己的博客网站 其实很简单 那么这篇文章会教会你如何进行搭建 二 介绍一些开源免
  • Yolov8网络详解与实战(附数据集)

    文章目录 摘要 模型详解 C2F模块 Loss head部分 模型实战 训练COCO数据集 下载数据集 COCO转yolo格式数据集 适用V4 V5 V6 V7 V8 配置yolov8环境 训练 断点训练 测试 训练自定义数据集 Label
  • 回发或回调参数无效,如何解决

    回发或回调参数无效 在配置中使用
  • 【MyIbatis】MyBatis 核心配置综述之Executor

    1 概述 转载 MyBatis 核心配置综述之Executor 上一篇我们对SqlSession和SqlSessionFactory的创建过程有了一个详细的了解 但上述的创建过程只是为SQL执行和SQL映射做了基础的铺垫而已 就和我们Spr