Mybatis源码解析(六):查询数据库主流程
创始人
2024-02-18 14:54:43
0

Mybatis源码系列文章

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

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

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

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

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

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

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

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


目录

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


前言

  • 上个文章讲到了查询入口,先查二级缓存,再查一级缓存,最后才会查询数据库
  • 本篇文章围绕mybatis如何封装底层jdbc的查询操作
  • 之后的源码对照的下图结合看,在源码中都能看到相同的代码
    在这里插入图片描述

一、执行流程及组件

处理流程

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

在这里插入图片描述

处理流程

在这里插入图片描述

  • 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  List 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. 关闭statementcloseStatement(stmt);}
}

三、创建语句处理器

进入newStatementHandler方法

  • 前面说到这个路由语句处理器;无论创建哪个语句处理器,外面都要包一层路由处理器
  • 插件会拦截不同的处理器,做自定义操作,后续章节单独讲
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {// 创建路由功能的StatementHandler,根据MappedStatement中的StatementTypeStatementHandler 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 handleResultSets(Statement stmt) throws SQLException {ErrorContext.instance().activity("handling results").object(mappedStatement.getId());//创建结果容器final List multipleResults = new ArrayList<>();int resultSetCount = 0;// 这里是获取第一个结果集,将传统JDBC的ResultSet包装成一个包含结果列元信息的ResultSetWrapper对象ResultSetWrapper rsw = getFirstResultSet(stmt);// 这里是获取所有要映射的ResultMap(按照逗号分割出来的)List 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++;}// 对应