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


    Mybatis源码系列文章

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

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

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

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

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

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

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

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

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

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

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



    前言

    • 上个文章讲到了查询入口,先查二级缓存,再查一级缓存,最后才会查询数据库
    • 本篇文章围绕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 <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);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    三、创建语句处理器

    进入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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    进入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++;
      }
    
      // 对应