Mybatis源码解析(七):查询数据库主流程

Mybatis源码系列文章

手写源码(了解源码整体流程及重要组件)

Mybatis源码解析(一):环境搭建

Mybatis源码解析(二):全局配置文件的解析

Mybatis源码解析(三):映射配置文件的解析

Mybatis源码解析(四):sql语句及#{}、${}的解析

Mybatis源码解析(五):SqlSession会话的创建

Mybatis源码解析(六):缓存执行器操作流程

Mybatis源码解析(七):查询数据库主流程

Mybatis源码解析(八):Mapper代理原理

Mybatis源码解析(九):插件机制

Mybatis源码解析(十):一级缓存和二级缓存


目录

  • 前言
  • 一、执行流程及组件
  • 二、查询数据库解析入口
  • 三、创建语句处理器
  • 四、创建statement以及参数化设置
    • 1、获取数据库连接
    • 2、创建Statement对象
    • 3、参数化处理
  • 五、执行Sql
  • 六、结果集处理
    • 1、获取ResultSet
    • 2、结果集转换pojo
  • 总结


前言

  • 上个文章讲到了查询入口,先查二级缓存,再查一级缓存,最后才会查询数据库
  • 本篇文章围绕mybatis如何封装底层jdbc的查询操作
  • 之后的源码对照的下图结合看,在源码中都能看到相同的代码
    Mybatis源码解析(七):查询数据库主流程_第1张图片

一、执行流程及组件

处理流程

  • sqlSession调用方法,查询数据库操作会交给不同类型的执行器Executor
  • 执行器会将任务交给不同类型的语句处理器StatementHandler(JDBC statement进行了封装)
  • 入参和返回结果分别由ParameterHandler和ResultSetHandler处理器,而真正执行操作的是类型处理器TypeHandler

Mybatis源码解析(七):查询数据库主流程_第2张图片

处理流程

Mybatis源码解析(七):查询数据库主流程_第3张图片

  • BaseStatementHandler:基础语句处理器(抽象类),它基本把语句处理器接口的核心部分都实现了,包括配置绑定、执行器绑定、映射器绑定、参数处理器构建、结果集处理器构建、语句超时设置、语句关闭等,并另外定义了新的方法 instantiateStatement 供不同子类实现以便获取不同类型的语句连接,子类可以普通执行 SQL 语句,也可以做预编译执行,还可以执行存储过程等
  • SimpleStatementHandler:普通语句处理器,继承 BaseStatementHandler 抽象类,对应 java.sql.Statement 对象的处理,处理普通的不带动态参数运行的 SQL,即执行简单拼接的字符串语句,同时由于Statement的特性,SimpleStatementHandler 每次执行都需要编译 SQL (注意:我们知道 SQL 的执行是需要编译和解析的)
  • PreparedStatementHandler:预编译语句处理器,继承 BaseStatementHandler 抽象类,对应 java.sql.PrepareStatement 对象的处理,相比上面的普通语句处理器,它支持可变参数 SQL 执行,由于PrepareStatement的特性,它会进行预编译,在缓存中一旦发现有预编译的命令,会直接解析执行,所以减少了再次编译环节,能够有效提高系统性能,并预防 SQL 注入攻击(所以是系统默认也是我们推荐的语句处理器)
  • CallableStatementHandler:存储过程处理器,继承 BaseStatementHandler 抽象类,对应 java.sql.CallableStatement 对象的处理,很明了,它是用来调用存储过程的,增加了存储过程的函数调用以及输出/输入参数的处理支持
  • RoutingStatementHandler:路由语句处理器,直接实现了 StatementHandler 接口,作用如其名称,确确实实只是起到了路由功能,并把上面介绍到的三个语句处理器实例作为自身的委托对象而已,所以执行器在构建语句处理器时,都是直接 new 了 RoutingStatementHandler 实例

二、查询数据库解析入口

  • 默认使用简单执行器,所以这里是SimpleExecutor的doQuery方法
  • newStatementHandler:创建语句处理器StatementHandler
  • prepareStatement:创建Statement对象及参数化设置(赋值?)
  • handler.query:向数据库发出sql执行,返回结果集
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  Statement stmt = null;
  try {
    // 1.获取配置实例
    Configuration configuration = ms.getConfiguration();
    // 2. new一个StatementHandler实例
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    // 3. 准备处理器,主要包括创建statement以及动态参数的设置
    stmt = prepareStatement(handler, ms.getStatementLog());
    // 4. 执行真正的数据库操作调用
    return handler.query(stmt, resultHandler);
  } finally {
    // 5. 关闭statement
    closeStatement(stmt);
  }
}

三、创建语句处理器

进入newStatementHandler方法

  • 前面说到这个路由语句处理器;无论创建哪个语句处理器,外面都要包一层路由处理器
  • 插件会拦截不同的处理器,做自定义操作,后续章节单独讲
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  // 创建路由功能的StatementHandler,根据MappedStatement中的StatementType
  StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
  // 插件机制:对核心对象进行拦截
  statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
  return statementHandler;
}

进入RoutingStatementHandler构造方法

  • 这里很明显看出路由处理器的作用,就是分发转换到不同的语句处理器
  • StatementType是从MappedStatement中获取,则每个返回值属性是resultType还是resultMap,都会在xml解析时,封装成ResultMap对象
  • ResultMap这里是集合,存储过程才有多结果集,才会getNextResultSet
  • handleResultSet:将结果集封装到pojo
  • collapseSingleResultList:multipleResults如果只有一个结果集(存储过程可能有多个),则get(0),返回则是我们需要的结果
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
  ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

  //创建结果容器
  final List<Object> multipleResults = new ArrayList<>();

  int resultSetCount = 0;
  // 这里是获取第一个结果集,将传统JDBC的ResultSet包装成一个包含结果列元信息的ResultSetWrapper对象
  ResultSetWrapper rsw = getFirstResultSet(stmt);

  // 这里是获取所有要映射的ResultMap(按照逗号分割出来的)
  List<ResultMap> resultMaps = mappedStatement.getResultMaps();
  // 要映射的ResultMap的数量
  int resultMapCount = resultMaps.size();
  validateResultMapsCount(rsw, resultMapCount);
  // 循环处理每个ResultMap,从第一个开始处理
  while (rsw != null && resultMapCount > resultSetCount) {
    // 得到结果映射信息(取出第一个结果集)
    ResultMap resultMap = resultMaps.get(resultSetCount);
    /*
     *  根据映射规则对结果集进行pojo转化(最后放入multipleResults结果集中)
     */
    handleResultSet(rsw, resultMap, multipleResults, null);
    // 处理下个结果集
    rsw = getNextResultSet(stmt);
    cleanUpAfterHandlingResultSet();
    resultSetCount++;
  }

  // 对应