Mybatis流程分析(八): 从JDBC出发,透彻分析Mybatis执行sql的原理

本系列文章皆在从细节 着手,由浅入深的分析Mybatis框架内部的处理逻辑,带你从一个全新的角度来认识Mybatis的工作原理。

思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。

作者:毅航😜


前言

在前一章Mybatis流程分析(七): 探寻Mybatis中执行sql语句的"入口"中,我们从MapperProxy中的invoke方法入手,逐一梳理invoke方法的调用逻辑,由浅入深的分析了Mybaits内部执行sql的语句的入口位置。具体来看,Mybatis中执行sql语句会委托给SqlSession中的select。进一步,sql语句的执行又将委托给Executor中的query方法。

接下来,我们便看看Executor究竟是如何来执行我们的sql语句的。但在开始分析Executor是如何执行sql之前,不妨让先来回顾一下通过原生的JDBC是如何完成数据库信息操纵的。

JDBC操纵数据库相关逻辑

java 复制代码
public class JDBCDemo {
   
    // .... 省略驱动加载
    
    // 1. 建立数据库连接
   Connection connection = DriverManager.getConnection(url, username, password);
            
    // 2. 创建SQL语句
    Statement statement = connection.createStatement();
    
    // 3. 执行SQL查询
    String selectSql = "SELECT * FROM yourtable";
    ResultSet resultSet = statement.executeQuery(selectSql);

    //4. 处理查询结果
    while (resultSet.next()) {
        int id = resultSet.getInt("id");
        String name = resultSet.getString("name");
      // 处理封装数据
       }
    }
}

可以看到,当用原生JDBC操纵数据库时,会经历如下几个步骤:

  1. 建立数据库连接 :使用DriverManager类的getConnection方法来建立与数据库的连接。你需要提供数据库的URL、用户名和密码。
  2. 创建SQL语句 :使用SQL语句来执行数据库操作。你可以创建StatementPreparedStatementCallableStatement对象,分别用于执行不同类型的SQL语句。
  3. 执行SQL查询 :使用创建的StatementPreparedStatement对象来执行SQL查询。
  4. 处理查询结果 :通过ResultSet对象来处理查询的结果数据。

接下来我们便重新将分析的重心拉回到Executor是如何来执行sql语句的上来。

Executor相关介绍

众所周知,在 MyBatis 中,执行器Executor 是一个关键的组件,它的作用是管理 SQL 语句的执行过程。此外,MyBatis 使用 Executor 来协调数据库的访问,以及将 SQL 语句映射到对应的数据库操作。

进一步,ExecutorMyBatis 中起到了桥梁的作用,连接了应用程序和数据库之间的交互。此外,Mybatis中执行器Executor的继承关系如下所示:

为了方便后续内容的分析,此处对其中的几个常用的执行器进行一下简单的介绍:

  1. 简单执行器SimpleExecutor :每执⾏⼀次updateselect,就开启⼀个 Statement对象,⽤完⽴刻关闭Statement对象。如果多次执⾏同⼀sql多次,则每次都进行预编译处理。
  2. 重⽤执⾏器ReuseExecutor :执⾏updateselect,会以sql作为key查找 Statement对象,存在就使⽤,不存在就创建,⽤完后,不关闭Statement 对象,⽽是放置于Map内,供下⼀次使用。即多次执⾏同⼀sql不重复进⾏预编译处理。
  3. 缓存执行器CachingExecutor :其属于装饰设计模式的典型应用,具体来看,其内部会持有一个其他类型的执行器(SimpleExecutorReuseExecutor)。当查询数据时,其会先从缓存中获取查询结果,存在就返回,不存在再委托给持有的Executor 去数据库查找。

知晓了执行器Mybatis的类型后,接下来,我们便看看执行器内部究竟是如何来执行sql语句的。

执行逻辑分析

在上一章Mybatis流程分析(七): 探寻Mybatis中执行sql语句的"入口"中,我们曾提到过,sql执行的入口在SqlSessionselect方法中。进一步,其又会将查询逻辑委托给Executor来执行,具体代码逻辑如下:

DefaultSqlSession # select

java 复制代码
public void select(String statement, Object parameter, 
                    RowBounds rowBounds, ResultHandler handler) {
     <1> 根据namespce+methodId获取对应的MappedStatement
    MappedStatement ms = configuration.getMappedStatement(statement);
    <2> 调用执行器中Executor中的query方法
    executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    // .... 省略其他无关逻辑
 
}

executor.query内部执行逻辑如下所示:

java 复制代码
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {

  // ....省略其他无关逻辑

  // 从数据库中查询相关信息
   list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); 

  // ....省略其他无关逻辑
    return list;
  }

可以看到,在Executor中的query方法内部,会将相关的处理逻辑交给queryFromDatabase来完成。为了方便理解,此处直接贴出内部调用链相关逻辑。

事实上,如果我们不对Configuration中的Executore类型进行配置的话,Mybatis默认会构建一个SimpleExector类型的执行器。所以此处我们选取SimpleExector中的doQuery方法来进行分析。

java 复制代码
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    // 获取环境对象Configuration
    Configuration configuration = ms.getConfiguration();
    // 构建一个Statement对象信息
    StatementHandler handler = configurationnewStatementHandler(wrapper, ms, parameter, rowBounds,resultHandler, boundSql);
    stmt = prepareStatement(handler, ms.getStatementLog());
    // 调用StatementHandler的query方法
    return handler.query(stmt, resultHandler);
    
  }

可以看到,在SimpleExecutor方法中,其并不是真正执行sql的地方。其又会将逻辑委托给StatementHandler对象来完成。相关逻辑如下:

java 复制代码
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    String sql = boundSql.getSql();
    // 通过对象statemnt中的exectute方法来完成sql的执行
    statement.execute(sql);
    // 结果集处理
    return resultSetHandler.handleResultSets(statement);
  }

看到此,其实开头的问题已经很清楚了。原来Mybatis中执行sql的底层逻辑和使用原生JDBC操纵的逻辑类似,其会将sql语句交给Statement来处理,当sql处理完毕后,再交由结果处理集ResultSetHandler来对结果进行处理。

解答疑问

在上一章分析中,我们曾提出这样一个问题。在DefaultSqlSession中定义的select方法其返回值为一个void,那Mybatis为什么会这样设计呢?

如下是我个人读源码的一些见解,仅供参考。通过本章分析我们知道,在Mybais中,SqlSession中的select会将处理逻辑委托给Executor中的query方法,但是query方法是有返回值的(List<T>)。

笔者猜测这样做的目的是为了与 insertupdatedelete 等方法保持一致性。因为这些方法通常不会返回实体对象。这样设计的种一致性可以使 API 设计更加简单和统一。虽然 select 方法本身没有返回结果,但它仍然可以通过查询方法委托给 Executor 来获取结果。

众所周知,MyBatis 中的 Executor 负责执行 SQL 语句并返回结果。在 Executor 中的 query 方法通常具有返回值,因为查询操作通常需要返回一个结果集(ResultSet)或者一些数据对象。但是,SqlSession 并不需要直接返回查询结果,因为它的职责是提供一种高级别的 API,隐藏了底层数据库操作的细节。具体的查询结果可以由调用 SqlSession 的客户端代码来处理。

此外,设计成这种方式还有一个好处,即灵活性和可扩展性。MyBatis 允许用户在配置文件中配置不同的 Executor 实现,例如 SimpleExecutorReuseExecutorBatchExecutor,这些实现可以根据需要执行查询,而 SqlSession 作为抽象接口不需要关心底层的具体执行细节。这种分离使得MyBatis可以灵活地适应不同的使用场景和性能需求。

总而言之,虽然 select 方法本身没有返回值,但是可以通过调用 Executorquery 方法来获取查询结果,然后在调用 select 方法的客户端代码中进行处理和使用这些结果。这种设计使 MyBatisAPI 更加清晰和一致。

相关推荐
小鑫记得努力5 分钟前
Java类和对象(下篇)
java
binishuaio9 分钟前
Java 第11天 (git版本控制器基础用法)
java·开发语言·git
zz.YE11 分钟前
【Java SE】StringBuffer
java·开发语言
老友@11 分钟前
aspose如何获取PPT放映页“切换”的“持续时间”值
java·powerpoint·aspose
颜淡慕潇20 分钟前
【K8S问题系列 |1 】Kubernetes 中 NodePort 类型的 Service 无法访问【已解决】
后端·云原生·容器·kubernetes·问题解决
wrx繁星点点26 分钟前
状态模式(State Pattern)详解
java·开发语言·ui·设计模式·状态模式
Upaaui29 分钟前
Aop+自定义注解实现数据字典映射
java
zzzgd81629 分钟前
easyexcel实现自定义的策略类, 最后追加错误提示列, 自适应列宽,自动合并重复单元格, 美化表头
java·excel·表格·easyexcel·导入导出
友善的鸡蛋30 分钟前
解决:使用EasyExcel导入Excel模板时出现数据导入不进去的问题
java·easyexcel·excel导入
星沁城31 分钟前
240. 搜索二维矩阵 II
java·线性代数·算法·leetcode·矩阵