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 更加清晰和一致。

相关推荐
豌豆花下猫2 分钟前
Python 潮流周刊#78:async/await 是糟糕的设计(摘要)
后端·python·ai
YMWM_4 分钟前
第一章 Go语言简介
开发语言·后端·golang
y25088 分钟前
《Object类》
java·开发语言
曙曙学编程9 分钟前
初级数据结构——树
android·java·数据结构
BestandW1shEs15 分钟前
彻底理解消息队列的作用及如何选择
java·kafka·rabbitmq·rocketmq
爱吃烤鸡翅的酸菜鱼17 分钟前
Java算法OJ(8)随机选择算法
java·数据结构·算法·排序算法
码蜂窝编程官方20 分钟前
【含开题报告+文档+PPT+源码】基于SpringBoot+Vue的虎鲸旅游攻略网的设计与实现
java·vue.js·spring boot·后端·spring·旅游
Viktor_Ye37 分钟前
高效集成易快报与金蝶应付单的方案
java·前端·数据库
hummhumm39 分钟前
第 25 章 - Golang 项目结构
java·开发语言·前端·后端·python·elasticsearch·golang
一二小选手43 分钟前
【Maven】IDEA创建Maven项目 Maven配置
java·maven