该系列文档是本人在学习 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处理器接口,代码如下:

public interface StatementHandler {
/**
* 准备操作,可以理解成创建 Statement 对象
*
* @param connection Connection 对象
* @param transactionTimeout 事务超时时间
* @return Statement 对象
*/
Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException; /**
* 设置 Statement 对象的参数
*
* @param statement Statement 对象
*/
void parameterize(Statement statement) throws SQLException; /**
* 添加 Statement 对象的批量操作
*
* @param statement Statement 对象
*/
void batch(Statement statement) throws SQLException; /**
* 执行写操作
*
* @param statement Statement 对象
* @return 影响的条数
*/
int update(Statement statement) throws SQLException; /**
* 执行读操作
*
* @param statement Statement 对象
* @param resultHandler ResultHandler 对象,处理结果
* @param <E> 泛型
* @return 读取的结果
*/
<E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException; /**
* 执行读操作,返回 Cursor 对象
*
* @param statement Statement 对象
* @param <E> 泛型
* @return Cursor 对象
*/
<E> Cursor<E> queryCursor(Statement statement) throws SQLException; /**
* @return BoundSql 对象
*/
BoundSql getBoundSql(); /**
* @return ParameterHandler 对象
*/
ParameterHandler getParameterHandler(); }

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

RoutingStatementHandler

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

public class RoutingStatementHandler implements StatementHandler {

	private final StatementHandler delegate;

	public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) {
// 根据不同的类型,创建对应的 StatementHandler 实现类
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
}
  • 在构造函数中初始化delegate委托对象,根据MappedStatement(每个SQL对应的对象)的statementType类型,创建对应的StatementHandler实现类

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

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

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

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

BaseStatementHandler

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

构造方法

public abstract class BaseStatementHandler implements StatementHandler {

    /**
* 全局配置
*/
protected final Configuration configuration;
/**
* 实例工厂
*/
protected final ObjectFactory objectFactory;
/**
* 类型处理器注册表
*/
protected final TypeHandlerRegistry typeHandlerRegistry;
/**
* 执行结果处理器
*/
protected final ResultSetHandler resultSetHandler;
/**
* 参数处理器,默认 DefaultParameterHandler
*/
protected final ParameterHandler parameterHandler;
/**
* 执行器
*/
protected final Executor executor;
/**
* SQL 相关信息
*/
protected final MappedStatement mappedStatement;
/**
* 分页条件
*/
protected final RowBounds rowBounds;
/**
* SQL 语句
*/
protected BoundSql boundSql; protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject,
RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory(); // <1> 如果 boundSql 为空,更新数据库的操作这里传入的对象会为 null
if (boundSql == null) { // issue #435, get the key before calculating the statement
// <1.1> 生成 key,定义了 <selectKey /> 且配置了 order="BEFORE",则在 SQL 执行之前执行
generateKeys(parameterObject);
// <1.2> 创建 BoundSql 对象
boundSql = mappedStatement.getBoundSql(parameterObject);
} this.boundSql = boundSql; // <2> 创建 ParameterHandler 对象,默认为 DefaultParameterHandler
// PreparedStatementHandler 实现的 parameterize 方法中需要对参数进行预处理,进行参数化时需要用到
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
// <3> 创建 DefaultResultSetHandler 对象
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
}

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

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

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

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

      只有配置的<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. docker 启动mysql 挂载宿主机目录

        在使用docker run 运行镜像获取容器时,有些容器会自动产生一些数据,为了这些数据会因为container (容器)的消失而消失,保证数据的安全,比如mysql 容器在运行中产生的一些表的数据, ...

      2. 树和堆(julyedu网课整理)

        date: 2018-12-05 16:59:15 updated: 2018-12-05 16:59:15 树和堆(julyedu网课整理) 1 定义 1.1 树的定义 它是由n(n>=1)个 ...

      3. B. Psychos in a Line 解析(思維、單調棧)

        Codeforce 319 B. Psychos in a Line 解析(思維.單調棧) 今天我們來看看CF319B 題目連結 題目 給一個數列,如果相鄰兩數,左邊大於右邊,那麼就可以殺死右邊的數字 ...

      4. Spark性能调优的方法

        原则一:避免创建重复的RDD 通常来说,我们在开发一个Spark作业时,首先是基于某个数据源(比如Hive表或HDFS文件)创建一个初始的RDD:接着对这个RDD执行某个算子操作,然后得到下一个RDD ...

      5. Centos7 使用nginx部署vue项目

        一.安装nginx #设置源 sudo rpm -Uvh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0 ...

      6. Luogu P4546 [THUWC2017]在美妙的数学王国中畅游

        题意 题意奇奇怪怪,这里就不写了. \(\texttt{Data Range:}1\leq n\leq 10^5,1\leq m\leq 2\times 10^5\) 题解 为什么你们都是卡在数学方面 ...

      7. VC获取文件后缀名

        VC获取文件后缀名 2011-07-28 10:30:50|  分类: Visual C++ and O |  标签: |举报 |字号大中小 订阅     1. CString GetSuffix(C ...

      8. .netcore基于mysql的codefirst

        .netcore基于mysql的codefirst 此文仅是对于netcore基于mysql的简单的codefirst实现的简单记录.示例为客服系统消息模板的增删改查实现 第一步.创建实体项目,并在其 ...

      9. NOIP 2012 P1081 开车旅行

        倍增 这道题最难的应该是预处理... 首先用$set$从后往前预处理出每一个点海拔差绝对值得最大值和次大值 因为当前城市的下标只能变大,对于点$i$,在$set$中二分找出与其值最接近的下标 然后再$ ...

      10. Java_大体介绍(超级短的那种)

        Java三大版本 Java SE: Java Standard Edition, 定位于客户端, 用于桌面应用软件编程 Java ME: Java Micro Edition, 用于嵌入式系统开发 J ...