从 01 开始 从 01 开始
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • RSS
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金等
  • 职场规划
  • 关于离职
  • 杂谈
  • 📖 读书

    • 读书工具
    • 读书笔记
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档

晓林

程序猿,自由职业者,博主,英语爱好者,健身达人
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • RSS
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金等
  • 职场规划
  • 关于离职
  • 杂谈
  • 📖 读书

    • 读书工具
    • 读书笔记
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档
  • JavaSE

  • JavaSenior

  • JavaEE

  • JavaWeb

  • Spring

  • 主流框架

    • Redis

    • Mybatis

      • Mybatis 介绍
      • Mybatis 入门案例
      • Mybatis 入门案例-注解
      • Mybatis 入门案例-实现类
      • Mybatis 内部执行原理概述
      • 实现一个微型的 Mybatis-配置文件版
      • 实现一个微型的 Mybatis-注解版
      • Mybatis 实现 CRUD
      • Mybatis 中传递对象参数
      • Mybatis 中的列名和属性名的映射
      • Mybatis 实现 DAO 层的开发
      • Mybatis 实现类的执行过程-查询方法
        • 打断点
        • SqlSession
        • Executor
        • Handler
        • 梳理
        • 代理对象
      • properties 标签的使用及细节
      • typeAliases 标签和 package 标签
      • Mybatis 连接池和事务
      • Mybatis 与 JNDI
      • Mybatis 中的动态 SQL
      • Mybatis 多表查询
      • Mybatis 中的多对多查询
      • Mybatis 的延迟加载
      • Mybatis 的缓存
      • Mybatis 的注解开发-CRUD
      • Mybatis 的注解开发-多表查询
    • Lucene

    • Elasticsearch

    • MQ

    • MyCat

    • Lombok

  • SpringMVC

  • SpringBoot

  • Java
  • 主流框架
  • Mybatis
2023-04-25
目录

Mybatis 实现类的执行过程-查询方法

# 110.Mybatis 实现类的执行过程-查询方法

我们从上一节的 DAO 的源码,一步步分析代码执行的过程

# 打断点

我们在 MybatisTestImpl 的 testFindAll 方法上打一个断点:

在 UserDaoImpl 类的 findAll 上也打一个:

‍

然后我们 debug 运行:可以在 test 方法上右键--调试 xxx :

# SqlSession

调试的过程中,首先我们可以看到实现类分别是 DefaultSqlSessionFactory 和 DefaultSqlSession

知道这个后,我们先停止调试。 ‍ 我们点进 SqlSession 的 源码,然后寻找它的实现类:在类名上右键--diagram--show diagram,例如:

‍

可以看到这样的图:这两个接口都是 java 提供的,用来关闭资源的接口

‍

我们回到 SqlSession,可以查看其实现类:

根据之前的 debug,我们选择 DefaultSqlSession:

‍

然后我们就可以看到其源码了:

我们观察 selectList 方法:可以看到有 4 个重载

public <E> List<E> selectList(String statement) {
    return this.selectList(statement, (Object)null);
}

public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
}

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    return this.selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
}

private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    List var6;
    try {
        MappedStatement ms = this.configuration.getMappedStatement(statement);
        this.dirty |= ms.isDirtySelect();
        var6 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, handler);
    } catch (Exception var10) {
        throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var10, var10);
    } finally {
        ErrorContext.instance().reset();
    }

    return var6;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

‍ 可以看到,前 3 个 selectList 方法,最终调用的都是第 4 个 selectList 方法;而该方法通过调用 executor.query 方法返回数据;我们点进 query 方法,可以看到这又是一个接口:

public interface Executor {
}
1
2

‍

# Executor

为了看到具体是哪个实现类,我们打个断点:

‍

通过断点调试,我们看到其调用的实现类是 CachingExecutor

‍

我们找到有 4 个方法重载的 query 方法:

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
1
2
3
4
5

‍ 注:我们可以在这里加个断点,然后继续调试,可以看到确实是执行了这个 query 方法:

‍

可以看到 query 最后又调用了一个 6 个参数的 query 方法,而该 query 方法最后调用的是 delegate.query 方法

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    //..........
    return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
1
2
3
4

‍ 可以看到最后 delegate,实现类是 SimpleExecutor

‍

SimpleExecutor 类中,没有 query 方法,这是因为 BaseExecutor 里有 query 方法,该方法最终调用的就是 doQuery 方法,该方法是抽象方法,最终被 SimpleExecutor 实现:

public class SimpleExecutor extends BaseExecutor {

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;

        List var9;
        try {
            Configuration configuration = ms.getConfiguration();
            StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            stmt = this.prepareStatement(handler, ms.getStatementLog());
            var9 = handler.query(stmt, resultHandler);
        } finally {
            this.closeStatement(stmt);
        }

        return var9;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

‍ 该 doquery 方法最终调用的是

handler.query(stmt, resultHandler)
1

‍ 而这个 Handler 是 RoutingStatementHandler:

‍

# Handler

我们通过查找 StatementHandler 的实现类 RoutingStatementHandler,可以看到其还是有 query 方法:

而最后调用的就是 PreparedStatementHandler 的 query 方法

该 query 方法的内容如下:

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement)statement;
    ps.execute();
    return this.resultSetHandler.handleResultSets(ps);
}
1
2
3
4
5

‍ 这个 ps 是一个代理对象:execute 能执行任意的 SQL,我们在讲解 JDBC 时讲过了:JDBC 常用类介绍 (opens new window)

执行完后做了结果集的封装,ResultSetHandler 接口只有一个实现类: DefaultResultSetHandler。handleResultSets 方法部分源码如下:

while(rsw != null && resultMapCount > resultSetCount) {
    ResultMap resultMap = (ResultMap)resultMaps.get(resultSetCount);
    this.handleResultSet(rsw, resultMap, multipleResults, (ResultMapping)null);
    rsw = this.getNextResultSet(stmt);
    this.cleanUpAfterHandlingResultSet();
    ++resultSetCount;
}
1
2
3
4
5
6
7

也就是获取每一行的数据,然后第 3 行那里调用 handleResultSet 处理每一行的数据,例如根据列名和 Java 类名进行映射;

# 梳理

我们在 MybatisTestImpl 里调用了 Dao 实现类的 session.selectList 方法:

public List<User> findAll() {
    SqlSession session = factory.openSession();
    List<User> users = session.selectList("com.peterjxl.dao.IUserDao.findAll");
    session.close();
    return users;
}
1
2
3
4
5
6

该 Session 是 DefaultSqlSession;由 DefaultSqlSessionFactory 工厂创建。

DefaultSqlSession 调用的 selectList 方法, 有很多个重载,但最终调用的是 Executor 的 query 方法

Executor 是个接口,实现类有 CachingExecutor,该类里也有多个重载的 query 方法,最终调用的是 delegate.query 方法;

delegate 是 SimpleExecutor 的实例,其 query 方法最后调用的是 RoutingStatementHandler 的方法;

RoutingStatementHandler 类里,就有一个 PreparedStatement 对象,并且调用 JDBC 的 execute 方法执行任何的 SQL,最后通过 resultSetHandler 封装返回的结果。

除了查询,删除和修改的执行过程我们就不一一演示了,也是类似的执行过程,并且其底层都是调用 JDBC 的接口

此外,断点调试和跟踪的方法,是我们在实际开发中经常用到的 ,希望读者们能好好掌握

# 代理对象

之前我们是自己实现的 DAO,调用了 Session 的 select 方法;而如果是代理对象呢?其内部是调用 selectList 方法的原理是什么呢?

我们根据之前的模式来,首先代理对象是通过 getMapper 方法获取的;

userDao = session.getMapper(IUserDao.class);
1

getMapper 是 session 接口的方法,我们得找其实现类 DefaultSqlSession 的源码,其 getMapper 方法如下:

public <T> T getMapper(Class<T> type) {
    return this.configuration.getMapper(type, this);
}
1
2
3

Configuration 里方法如下:

public MapperRegistry getMapperRegistry() {
    return this.mapperRegistry;
}
1
2
3

MapperRegistry 中,则创建了代理对象(第 7 行,熟悉的 newInstance)

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    } else {
        try {
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception var5) {
            throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

MapperProxyFactory 的源码:

protected T newInstance(MapperProxy<T> mapperProxy) {
    return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}

public T newInstance(SqlSession sqlSession) {
    MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
    return this.newInstance(mapperProxy);
}
1
2
3
4
5
6
7
8

‍ 也就是最后就是调用 Proxy 的 newProxyInstance 方法。因此我们的关注点,就是 mapperProxy,其内部应该是实现了增强方法的逻辑。

MapperProxy 部分源码:可以看到其实现了 InvocationHandler,并且有 invoke 方法:

public class MapperProxy<T> implements InvocationHandler, Serializable {

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
    }
}
1
2
3
4
5
6
7
8
9
10

‍ 观察 cachedInvoker,可以看到其用到了一个类 MapperMethod:

MapperMethod 部分源码如下:

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    Object param;
    switch (this.command.getType()) {
        case INSERT:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
            break;
        case UPDATE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
            break;
        case DELETE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
            break;
        case SELECT:

    //..... 省略其他
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

‍ 看到这里,大家应该都知道了,其最后就是根据 switch 判断执行什么 SQL,然后调用对应的方法,例如 sqlSession.insert,sqlSession.update。

而 insert,update 方法,最后的调用过程,跟我们本文前半部分讲的一样,也是 Executor 和 Handler。 ‍

上次更新: 2025/5/17 12:26:09
Mybatis 实现 DAO 层的开发
properties 标签的使用及细节

← Mybatis 实现 DAO 层的开发 properties 标签的使用及细节→

最近更新
01
吐槽一下《僵尸校园》
05-15
02
2025 年 4 月记
04-30
03
山西大同 “订婚强奸案” 将会给整个社会带来的影响有多严重? - 知乎 转载
04-26
更多文章>
Theme by Vdoing | Copyright © 2022-2025 | 粤 ICP 备 2022067627 号 -1 | 粤公网安备 44011302003646 号 | 点击查看十年之约
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式