该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址Mybatis-Spring 源码分析 GitHub 地址Spring-Boot-Starter 源码分析 GitHub 地址)进行阅读

MyBatis 版本:3.5.2

MyBatis-Spring 版本:2.0.3

MyBatis-Spring-Boot-Starter 版本:2.1.4

MyBatis的SQL执行过程

在前面一系列的文档中,我已经分析了 MyBatis 的基础支持层以及整个的初始化过程,此时 MyBatis 已经处于就绪状态了,等待使用者发号施令了

那么接下来我们来看看它执行SQL的整个过程,该过程比较复杂,涉及到二级缓存,将返回结果转换成 Java 对象以及延迟加载等等处理过程,这里将一步一步地进行分析:

MyBatis中SQL执行的整体过程如下图所示:

在 SqlSession 中,会将执行 SQL 的过程交由Executor执行器去执行,过程大致如下:

  1. 通过DefaultSqlSessionFactory创建与数据库交互的 SqlSession “会话”,其内部会创建一个Executor执行器对象
  2. 然后Executor执行器通过StatementHandler创建对应的java.sql.Statement对象,并通过ParameterHandler设置参数,然后执行数据库相关操作
  3. 如果是数据库更新操作,则可能需要通过KeyGenerator先设置自增键,然后返回受影响的行数
  4. 如果是数据库查询操作,则需要将数据库返回的ResultSet结果集对象包装成ResultSetWrapper,然后通过DefaultResultSetHandler对结果集进行映射,最后返回 Java 对象

上面还涉及到一级缓存二级缓存延迟加载等其他处理过程

SQL执行过程(二)之StatementHandler

在上一篇文档中,已经详细地分析了在MyBatis的SQL执行过程中,SqlSession会话将数据库操作交由Executor执行器去完成,实际上需要通过StatementHandler创建相应的Statement对象,并做一些准备工作,然后通过Statement执行数据库操作,查询结果则需要通过ResultSetHandler对结果集进行映射转换成Java对象,那么接下来我们先来看看StatementHandler到底做哪些操作

StatementHandler接口的实现类如下图所示:

  • org.apache.ibatis.executor.statement.RoutingStatementHandler:实现StatementHandler接口,装饰器模式,根据Statement类型创建对应的StatementHandler对象,所有的方法执行交由该对象执行

  • org.apache.ibatis.executor.statement.BaseStatementHandler:实现StatementHandler接口,提供骨架方法,指定的几个抽象方法交由不同的子类去实现

  • org.apache.ibatis.executor.statement.SimpleStatementHandler:继承BaseStatementHandler抽象类,创建java.sql.Statement进行数据库操作

  • org.apache.ibatis.executor.statement.PreparedStatementHandler:继承BaseStatementHandler抽象类,创建java.sql.PreparedStatement进行数据库操作(默认)

  • org.apache.ibatis.executor.statement.CallableStatementHandler:继承BaseStatementHandler抽象类,创建java.sql.CallableStatement进行数据库操作,用于存储过程

我们先回顾一下StatementHandler是在哪里被创建的,可以在《SQL执行过程(一)之Executor》SimpleExecutor小节中有讲到,创建的是RoutingStatementHandler对象,

StatementHandler

org.apache.ibatis.executor.statement.StatementHandler:Statement处理器接口,代码如下:

  1. public interface StatementHandler {
  2. /**
  3. * 准备操作,可以理解成创建 Statement 对象
  4. *
  5. * @param connection Connection 对象
  6. * @param transactionTimeout 事务超时时间
  7. * @return Statement 对象
  8. */
  9. Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException;
  10. /**
  11. * 设置 Statement 对象的参数
  12. *
  13. * @param statement Statement 对象
  14. */
  15. void parameterize(Statement statement) throws SQLException;
  16. /**
  17. * 添加 Statement 对象的批量操作
  18. *
  19. * @param statement Statement 对象
  20. */
  21. void batch(Statement statement) throws SQLException;
  22. /**
  23. * 执行写操作
  24. *
  25. * @param statement Statement 对象
  26. * @return 影响的条数
  27. */
  28. int update(Statement statement) throws SQLException;
  29. /**
  30. * 执行读操作
  31. *
  32. * @param statement Statement 对象
  33. * @param resultHandler ResultHandler 对象,处理结果
  34. * @param <E> 泛型
  35. * @return 读取的结果
  36. */
  37. <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;
  38. /**
  39. * 执行读操作,返回 Cursor 对象
  40. *
  41. * @param statement Statement 对象
  42. * @param <E> 泛型
  43. * @return Cursor 对象
  44. */
  45. <E> Cursor<E> queryCursor(Statement statement) throws SQLException;
  46. /**
  47. * @return BoundSql 对象
  48. */
  49. BoundSql getBoundSql();
  50. /**
  51. * @return ParameterHandler 对象
  52. */
  53. ParameterHandler getParameterHandler();
  54. }

每个方法可以根据注释先理解它的作用,在实现类中的会讲到

RoutingStatementHandler

org.apache.ibatis.executor.statement.RoutingStatementHandler:实现StatementHandler接口,采用装饰器模式,在初始化的时候根据Statement类型,创建对应的StatementHandler对象,代码如下:

  1. public class RoutingStatementHandler implements StatementHandler {
  2. private final StatementHandler delegate;
  3. public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
  4. ResultHandler resultHandler, BoundSql boundSql) {
  5. // 根据不同的类型,创建对应的 StatementHandler 实现类
  6. switch (ms.getStatementType()) {
  7. case STATEMENT:
  8. delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
  9. break;
  10. case PREPARED:
  11. delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
  12. break;
  13. case CALLABLE:
  14. delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
  15. break;
  16. default:
  17. throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
  18. }
  19. }
  20. }
  • 在构造函数中初始化delegate委托对象,根据MappedStatement(每个SQL对应的对象)的statementType类型,创建对应的StatementHandler实现类

  • 其余所有的方法都是直接交由delegate去执行的,这里就不列出来了,就是实现StatementHandler接口的方法

回顾到《MyBatis初始化(二)之加载Mapper接口与映射文件》中的XMLStatementBuilder小节,在parseStatementNode方法中的第10步如下:

  1. StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));

所以说statementType的默认值为PREPARED,委托对象也就是PreparedStatementHandler类型

BaseStatementHandler

org.apache.ibatis.executor.statement.BaseStatementHandler:实现StatementHandler接口,提供骨架方法,指定的几个抽象方法交由不同的子类去实现

构造方法

  1. public abstract class BaseStatementHandler implements StatementHandler {
  2. /**
  3. * 全局配置
  4. */
  5. protected final Configuration configuration;
  6. /**
  7. * 实例工厂
  8. */
  9. protected final ObjectFactory objectFactory;
  10. /**
  11. * 类型处理器注册表
  12. */
  13. protected final TypeHandlerRegistry typeHandlerRegistry;
  14. /**
  15. * 执行结果处理器
  16. */
  17. protected final ResultSetHandler resultSetHandler;
  18. /**
  19. * 参数处理器,默认 DefaultParameterHandler
  20. */
  21. protected final ParameterHandler parameterHandler;
  22. /**
  23. * 执行器
  24. */
  25. protected final Executor executor;
  26. /**
  27. * SQL 相关信息
  28. */
  29. protected final MappedStatement mappedStatement;
  30. /**
  31. * 分页条件
  32. */
  33. protected final RowBounds rowBounds;
  34. /**
  35. * SQL 语句
  36. */
  37. protected BoundSql boundSql;
  38. protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject,
  39. RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  40. this.configuration = mappedStatement.getConfiguration();
  41. this.executor = executor;
  42. this.mappedStatement = mappedStatement;
  43. this.rowBounds = rowBounds;
  44. this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
  45. this.objectFactory = configuration.getObjectFactory();
  46. // <1> 如果 boundSql 为空,更新数据库的操作这里传入的对象会为 null
  47. if (boundSql == null) { // issue #435, get the key before calculating the statement
  48. // <1.1> 生成 key,定义了 <selectKey /> 且配置了 order="BEFORE",则在 SQL 执行之前执行
  49. generateKeys(parameterObject);
  50. // <1.2> 创建 BoundSql 对象
  51. boundSql = mappedStatement.getBoundSql(parameterObject);
  52. }
  53. this.boundSql = boundSql;
  54. // <2> 创建 ParameterHandler 对象,默认为 DefaultParameterHandler
  55. // PreparedStatementHandler 实现的 parameterize 方法中需要对参数进行预处理,进行参数化时需要用到
  56. this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
  57. // <3> 创建 DefaultResultSetHandler 对象
  58. this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  59. }
  60. }

关于它的属性可以根据注释进行理解

  1. 如果入参中的boundSqlnull,则需要进行初始化,可以会看到SimpleExecutor中执行数据库的更新操作时,传入的boundSqlnull,数据库的查询操作才会传入该对象的值

    1. 调用generateKeys(Object parameter)方法,根据配置的KeyGenerator对象,在SQL执行之前执行查询操作获取值,设置到入参对象对应属性中,代码如下:

      1. protected void generateKeys(Object parameter) {
      2. /*
      3. * 获得 KeyGenerator 对象
      4. * 1. 配置了 <selectKey /> 则会生成 SelectKeyGenerator 对象
      5. * 2. 配置了 useGeneratedKeys="true" 则会生成 Jdbc3KeyGenerator 对象
      6. * 否则为 NoKeyGenerator 对象
      7. */
      8. KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
      9. ErrorContext.instance().store();
      10. // 前置处理,创建自增编号到 parameter 中
      11. keyGenerator.processBefore(executor, mappedStatement, null, parameter);
      12. ErrorContext.instance().recall();
      13. }

      只有配置的<selectKey />标签才有前置处理,这就是为什么数据库的更新操作传入的boundSqlnull的原因,因为入参中有的属性值可能需要提前生成一个值(执行配置的SQL语句),KeyGenerator会在后续讲到

      精尽MyBatis源码分析 - SQL执行过程(二)之 StatementHandler的更多相关文章

      1. 精尽MyBatis源码分析 - SQL执行过程(四)之延迟加载

        该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

      2. 精尽MyBatis源码分析 - SQL执行过程(三)之 ResultSetHandler

        该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

      3. 精尽 MyBatis 源码分析 - SqlSession 会话与 SQL 执行入口

        该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

      4. 精尽MyBatis源码分析 - MyBatis初始化(四)之 SQL 初始化(下)

        该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

      5. 精尽 MyBatis 源码分析 - 基础支持层

        该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

      6. 精尽MyBatis源码分析 - 插件机制

        该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

      7. 精尽MyBatis源码分析 - 文章导读

        该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

      8. MyBatis 源码篇-SQL 执行的流程

        本章通过一个简单的例子,来了解 MyBatis 执行一条 SQL 语句的大致过程是怎样的. 案例代码如下所示: public class MybatisTest { @Test public void ...

      9. 精尽MyBatis源码分析 - MyBatis-Spring 源码分析

        该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

      随机推荐

      1. JAVA递归实现线索化二叉树

        JAVA递归实现线索化二叉树 基础理论 首先,二叉树递归遍历分为先序遍历.中序遍历和后序遍历. 先序遍历为:根节点+左子树+右子树 中序遍历为:左子树+根节点+右子树 后序遍历为:左子树+右子树+根节 ...

      2. 蓝桥杯 Island Hopping Java代码

              问题描述 太平洋岛网公司(PLN)已经瞄向了太平洋中的一些群岛.这些群岛没有快捷的互联网连接.PLN计划向群岛提供互联网服务,以开发这个太平洋中潜在的市场.每组群岛的核心岛屿已经被深海电缆 ...

      3. Stream(一)

        public class Test06 { /* * StreamAPI: * StreamAPI是用来处理数据,处理集合等容器中的数据,处理操作有:查询.筛选.删除.过滤.统计.映射等. * 希望能 ...

      4. localStorage.getItem得到的是[object Object] 的解决方案

        设计背景: 购物车要实现本地存储,避免刷新页面数据丢失 实现方案: 1,本地储存,进入页面获取本地数据,在进行数据操作 2,每操作一次数据就将数据传给后台进行保存,(操作数据多,用户量大对服务器造成压 ...

      5. LC滤波器简单设计法 - 一文读懂LC滤波器简单设计方法及原理介绍,LC值计算方法

        LC滤波器概述 LC滤波器也称为无源滤波器,是传统的谐波补偿装置.LC滤波器之所以称为无源滤波器,顾名思义,就是该装置不需要额外提供电源.LC滤波器一般是由滤波电容器.电抗器和电阻器适当组合而成,与谐 ...

      6. E. Almost Regular Bracket Sequence 解析(思維)

        Codeforce 1095 E. Almost Regular Bracket Sequence 解析(思維) 今天我們來看看CF1095E 題目連結 題目 給你一個括號序列,求有幾個字元改括號方向 ...

      7. A. Cubes Sorting 解析(思維)

        Codeforce 1420 A. Cubes Sorting 解析(思維) 今天我們來看看CF1420 題目連結 題目 給一個數列\(a\),求能不能在不超過\(\frac{n(n-1)}{2}-1 ...

      8. Luogu P5087 数学

        题意 给定一个长度为 \(n\) 的序列 \(a_i\),求出在这个序列中所有选出 \(k\) 个元素方案中元素的乘积之和. \(\texttt{Data Range:}1\leq n\leq 10^ ...

      9. lora传输模块的特点概述

        现今Lora已经是一种在物联网中广泛应用的技术,它是一种无线调制的方式,相对于传统的FSK调制技术来说,Lora在抑制同频干扰方面有非常大的优势,它解决了无法同时兼顾距离.抗扰和功耗不足的问题;另外l ...

      10. 11张图和源码带你解析Spring Bean的生命周期,建议收藏~!

        在网上已经有跟多Bean的生命周期的博客,但是很多都是基于比较老的版本了,最近把整个流程画成了一个流程图.待会儿使用流程图,说明以及代码的形式来说明整个声明周期的流程.注意因为代码比较多,这里的流程图 ...