Mybatis源码01-Executor

news2024/11/16 4:28:14

前言

为了方便公司业务排查问题,要求打印执行的sql,以及执行时间。编写了一个Mybatis的拦截器,此前从未看过mybatis的源码,在调试的过程中不断阅读源码,后边想更深刻了解一下,看了鲁班大叔的视频,想做一下记录以及学习过程。

JDBC执行流程回顾

MyBatis是一个基于JDBC的数据库访问组件。首先回顾一下JDBC执行流程:

//1、导入jar包
//2、注册驱动 
Class.forName("com.mysql.jdbc.Driver");
//3、获取连接
Connection connection = DriverManager.getConnection(JDBC.URL, JDBC.USERNAME, JDBC.PASSWORD);
//4、获取执行者对象
Statement stat = conn.createStatement();
//5、预编译SQL,并接收返回结果
PreparedStatement statement = connection.prepareStatement("select * from  users ");
//6、执行查询
ResultSet resultSet = statement.executeQuery();
//7、处理结果
readResultSet(resultSet);
//8、释放资源,关闭连接等

现在这些操作Mybatis都帮我们完成了!

Mybatis执行流程

MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息。
在这里插入图片描述
本文主要介绍MyBatis 的核心 Executor,后续会详细介绍Mybatis执行一个sql的流程。

Executor

Executor位于executor包,Mybatis中所有的SQL命令都由它来调度执行,他主要用于连接 SqlSession与JDBC,所有与JDBC相关的操作都要通过它。图中展示了Executor在核心对象中所处位置。
在这里插入图片描述
Executor定义了查询、更新、事务、缓存操作相关的接口方法,Executor接口对外暴露,由SqlSession依赖,并受其调度与管理。

Executor家族:
在这里插入图片描述

  • BaseExecutor,它是一个抽象类,实现了大部分Executor的接口。它有三个子类,分别是SimpleExecutor、ReuseExecutor、BatchExecutor。BaseExecutor及其子类完成了一级缓存管理和与数据库交互有关的操作。
  • CachingExecutor,缓存执行器,Mybatis二级缓存的核心处理类。CachingExecutor持有一个BaseExecutor的实现类(SimpleExecutor、ReuseExecutor或BatchExecutor)实例作为委托执行器。它主要完成Mybatis二级缓存处理逻辑,当缓存查询中不存在或查询不到结果时,会通过委托执行器查询数据库。
  • 第三层是BaseExecutor的三个子类。简单执行器为默认执行器,具备执行器的所有能力;可重用执行器,是相对简单执行器而言的,它具备MappedStatement的缓存与复用能力,即在一个SqlSession会话内重复执行同一个命令,可以直接复用已缓存的MappedStatement;批量执行器,即一次可以执行多个命令。

Executor的核心功能是调度执行SQL,参与了全过程;为了提高查询性能,Mybatis在Executor中设计了一级缓存和二级缓存,一级缓存由BaseExecutor及其子类实现,二级缓存由CachingExecutor实现。一级缓存是默认开启的,二级缓存需要启用配置。由于CachingExecutor负责缓存管理,真正的数据库查询是由BaseExecutor完成的,所以对外来看,Executor有三种类型SIMPLE、REUSE、BATCH,默认是SIMPLE,我们可以在配置文件或创建SqlSession时指定参数修改默认执行器类型。以下为Mybatis中ExecutorType定义:

public enum ExecutorType {
    // 简单执行器
    SIMPLE, 
    // 可重用的执行器
    REUSE, 
    // 批处理执行器
    BATCH
}

执行过程

以查询为例,默认情况下会使用CachingExecutor,首先判断是否开启二级缓存,

 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.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    //默认情况没有开启二级缓存,会直接走到这里
    //delegate即BaseExecutor三个子类的其中一个
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

进入CachingExecutor#query,首先通过MappedStatement获取BoundSql,创建缓存key,然后调用了重载的query方法。重载query查询在不考虑缓存的情况下,会直接通过委托执行器的query方法进行查询。
这里的委托执行器为BaseExecutor的子类,而BaseExecutor实现了query方法,所以我们先进入BaseExecutor#query()(同样先忽略一级缓存部分的逻辑):

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.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      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) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

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

在源码中看到do开头的方法一定要注意,这是真正做事的方法。
SimpleExecutor#doQuery

  @Override
  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
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //准备Statement
      stmt = prepareStatement(handler, ms.getStatementLog());
      //执行查询
      return handler.query(stmt, resultHandler);
    } finally {
      //关闭Statement
      closeStatement(stmt);
    }
  }

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }

  • 从MappedStatement对象获取全局Configuration配置对象;
  • 调用Configuration#newStatementHandler创建StatementHandler对象;
  • 创建并初始化Statement对象;
  • 调用StatementHandler#query执行Statement,并使用resultHandler解析返回值;
  • 最后关闭Statement。

ReuseExecutor#doQuery

  //Statement缓存
  private final Map<String, Statement> statementMap = new HashMap<>();

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    Statement stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.query(stmt, resultHandler);
  }

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    BoundSql boundSql = handler.getBoundSql();
    String sql = boundSql.getSql();
    //检查缓存中是否存在当前sql
    if (hasStatementFor(sql)) {
      //如果有,就直接拿出来用
      stmt = getStatement(sql);
      applyTransactionTimeout(stmt);
    } else {
      //如果没有,就新建一个
      Connection connection = getConnection(statementLog);
      stmt = handler.prepare(connection, transaction.getTimeout());
      //然后,缓存起来。
      putStatement(sql, stmt);
    }
    handler.parameterize(stmt);
    return stmt;
  }

ReuseExecutor#doQuery与SimpleExecutor#doQuery的逻辑基本一致,不同点在于prepareStatement方法的实现逻辑。prepareStatement使用statementMap对执行过的sql进行缓存,只有statementMap中不存在当前sql的时候才会执行创建流程,对性能有一定的提升。需要注意的是,Executor对象是SqlSession的组成部分,所以这个缓存与SqlSession的生命周期一致。

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException {
    Statement stmt = null;
    try {
      //批量执行,目前我理解是为了把之前批量更新的语句执行掉
      flushStatements();
      //获取Configuration对象
      Configuration configuration = ms.getConfiguration();
      //创建StatementHandler
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);
      //创建Statement
      Connection connection = getConnection(ms.getStatementLog());
      stmt = handler.prepare(connection, transaction.getTimeout());
      //设置Statement参数
      handler.parameterize(stmt);
      //执行并返回结果
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

StatementHandler

用于获取预处理器,共有三种类型。通过statementType=“STATEMENT|PREPARED|CALLABLE” 可分别进行指定。
PreparedStatementHandler:带预处理的执行器
CallableStatementHandler:存储过程执行器
SimpleStatementHandler:基于Sql执行器
在这里插入图片描述

ResultSetHandler

用于处理和封装返回结果。可在SqlSession中查询时自行定义ResultSetHandler

执行时序
在这里插入图片描述

  • 通过Configuration获取StatementHandler实例(由statementType 决定)。
  • 通过事务获取连接
  • 创建JDBC Statement对像
  • 执行 JDBC Statement execute
  • 处理返回结果

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

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

相关文章

OSCP-Nickel(爆破pdf、本地http提权)

目录 扫描 HTTP 提权 扫描 FileZilla不接受匿名FTP登录。 端口21上的SSH和3389上的RDP很少是初始入口点,但是如果遇到一些凭据,可以记住这一点。 HTTP 打开Web浏览器并导航到端口8089和3333,用于的HTTP服务器。端口8089似乎是某种类型的开发环境。 单击一个按钮重定向到…

boot-admin整合Quartz实现动态管理定时任务

淄博烧烤爆红出了圈&#xff0c;当你坐在八大局的烧烤摊&#xff0c;面前是火炉、烤串、小饼和蘸料&#xff0c;音乐响起&#xff0c;啤酒倒满&#xff0c;烧烤灵魂的party即将开场的时候&#xff0c;你系统中的Scheduler&#xff08;调试器&#xff09;&#xff0c;也自动根据…

在函数中使用变量

shell脚本编程系列 向函数传递参数 函数可以使用标准的位置变量来表示在命令行中传给函数的任何参数。其中函数名保存在$0变量中&#xff0c;函数参数则依次保存在$1、$2等变量当中&#xff0c;也可以使用特殊变量$#来确定参数的个数 在脚本中调用函数时&#xff0c;必须将参…

day3 TCP/IP协议与五层体系结构

TCP / IP 四层体系结构 TCP / IP工作流程&#xff1a; 现在互联网使用的 TCP/IP 体系结构已经发生了演变&#xff0c;即某些应用程序可以直接使用 IP 层&#xff0c;或甚至直接使用最下面的网络接口层。 沙漏型展示&#xff1a; 五层体系结构 各层的主要功能 应用层&#xff1…

C++ Primer阅读笔记--语句的使用

① 空语句 最简单的语句是空语句&#xff0c;其只含有一个单独的分号&#xff1b; ② switch语句 case 关键字和它对应的值一起被称为 case 标签&#xff0c;case 标签必须是整型常量表达式&#xff1b; char ch getVal(); int iVal 42; switch(ch){case 3.14: // 错误&#…

ZmosHarmony buildroot移植与使用

前言 移植过程 1、添加编译选项编译buildroot。 2、开机启动时设置 LD库的环境变量与PATH路径。 是什么原因需要这样操作&#xff1f; 主要使用busybox&#xff0c;使用buildroot的瑞士军dao。 使用busybox 为buildroot下的使用 第一次启动时设置 由于是在vendor分区因此 …

01 openEuler虚拟化-KVM虚拟化简介

文章目录 01 openEuler虚拟化-KVM虚拟化简介1.1 简介1.2 虚拟化架构1.3 虚拟化组件1.4 虚拟化特点1.5 虚拟化优势1.6 openEuler虚拟化 01 openEuler虚拟化-KVM虚拟化简介 1.1 简介 在计算机技术中&#xff0c;虚拟化是一种资源管理技术&#xff0c;它将计算机的各种实体资源&…

ActiveMQ 反序列化漏洞 (CVE-2015-5254)漏洞复现

当前漏洞环境部署在vulhub,当前验证环境为vulhub靶场&#xff08;所有实验均为虚拟环境&#xff09; 实验环境&#xff1a;攻击机----kali 靶机&#xff1a;centos7 需要的jar包&#xff1a;jmet-0.1.0-all.jar 1、启动docker&#xff0c;进入vulhub&#xff08;靶机&#xff0…

centos主机测试io极限

这里使用fio工具来测试磁盘的io 1.安装fio命令 yum -y install fio 2.在需要测试的磁盘所挂载的目录下创建一个测试目录 由于我就只有一个磁盘&#xff0c;/目录也挂载在这个磁盘上&#xff0c;所以就直接在tmp目录里创建 mkdir /tmp/cs 3.创建一个名为 test.fio 的文件&a…

中级软件设计师备考---信息系统安全

目录 安全属性对称加密技术非对称加密技术信息摘要和数字签名数字信封和PGP各个网络层次的安全保障网络威胁与攻击防火墙技术 安全属性 保密性&#xff1a;最小授权原则、防暴露、信息加密、物理保密 完整性&#xff1a;安全协议、校验码、密码校验、数字签名、公证 可用性&a…

【 Spring 事务传播机制 】

文章目录 一、概念二、为什么需要事务传播机制&#xff1f;三、事务传播机制有哪些&#xff1f;四、Spring 事务传播机制使⽤和各种场景演示4.1 ⽀持当前事务&#xff08;REQUIRED&#xff09;4.2 不⽀持当前事务&#xff08;REQUIRES_NEW&#xff09;4.3 NESTED 嵌套事务4.4 嵌…

软考软件设计师 软件工程笔记

软件工程 CMM&#xff08;能力成熟度模型&#xff09;CMMI&#xff08;能力成熟度模型集成&#xff09;瀑布模型V模型&#xff08;质量保证&#xff09;增量模型演化模型&#xff08;迭代更新&#xff09;原型模型螺旋模型&#xff08;风险分析&#xff09;喷泉模型统一过程&am…

大数据编程实验二:熟悉常用的HDFS操作

实验目的 1、理解HDFS在Hadoop体系结构中的角色 2、熟悉使用HDFS操作常用的Shell命令 3、熟悉HDFS操作常用的Java API 实验平台 1、操作系统&#xff1a;Windows 2、Hadoop版本&#xff1a;3.1.3 3、JDK版本&#xff1a;1.84、Java IDE&#xff1a;IDEA 实验步骤 前期&#x…

Springboot整合WebSocket(纯后端)

文章目录 一、 HTTP协议与WebSocket区别二、客户端&#xff08;浏览器&#xff09;实现1、websocket对象2、websocket事件3、WebSocket方法 三、服务端实现1、连接过程2、服务端接收客户端消息3、服务端推送消息给客户端 四、后端功能实现 一、 HTTP协议与WebSocket区别 HTTP协…

如何在家自学编程成为一名程序员?

转自&#xff1a;如何在家自学编程&#xff0c;成为一名优秀的程序员&#xff1f; - 知乎 跟着黑马程序员学&#xff0c;自学也可以很优秀。先找到方向—>前/后端&#xff1f;测试&#xff1f;还是什么&#xff1f;—>找到相关的学习路线 —> 坚持不懈的学习 —> …

论文学习——Video LDM (Align your Latents)

Align your Latents: High-Resolution Video Synthesis with Latent Diffusion Models 0. 来源 本文是阅读论文后的个人笔记&#xff0c;适应于个人水平&#xff0c;叙述顺序和细节详略与原论文不尽相同&#xff0c;并不是翻译原论文。 如果想了解所有细节&#xff0c;建议移…

华为OD机试真题(Java),旋转数组的最小数字(100%通过+复盘思路)

一、题目描述 有一个长度为 n 的非降序数组&#xff0c;比如[1,2,3,4,5]&#xff0c;将它进行旋转&#xff0c;即把一个数组最开始的若干个元素搬到数组的末尾&#xff0c;变成一个旋转数组&#xff0c;比如变成了[3,4,5,1,2]&#xff0c;或者[4,5,1,2,3]这样的。请问&#xf…

Filter 的使用

把对资源的请求拦截下来&#xff0c;从而实现一些特殊功能 &#xff0c;比如需要先登录再使用其他功能 拦截对资源的请求 放行后&#xff0c;执行完资源&#xff0c;再执行放行后的逻辑 按字符比较升序排序&#xff0c;值小的优先级高 FilterDemo优先级高于FilterDemo2 Listene…

华为OD机试真题(Java),最长的连续子序列(100%通过+复盘思路)

一、题目描述 有N个正整数组成的一个序列&#xff0c;给定一个整数sum&#xff0c;求长度最长的的连续子序列使他们的和等于sum&#xff0c;返回该子序列的长度&#xff0c;如果没有满足要求的序列返回-1。 二、输入描述 第1行有N个正整数组成的一个序列。 第2行给定一个整…

[MySQL]基础知识笔记(数据库与表操作)

内存与硬盘的区别&#xff1a; 内存&#xff1a;容量小&#xff0c;速度快&#xff0c;造价高&#xff0c;断电后数据丢失硬盘&#xff1a;容量大&#xff0c;速度慢&#xff0c;造价低&#xff0c;断电后数据不丢失 常见的关系型数据库&#xff1a; 1.ACCESS-微软出的在OFF…