(16)MyBatis执行流程分析(偏上层架构)

🧭 一句话总览

MyBatis 执行一条 SQL 的核心流程是:
SqlSession → MapperProxy → Executor → StatementHandler → ResultSetHandler

下面逐层拆解(附关键源码逻辑):


🔁 一、整体执行流程图(简化版)

复制代码
[Mapper Interface]
        ↓ (JDK动态代理)
[MapperProxy.invoke()]
        ↓
[SqlSession.selectOne(...)]
        ↓
[Configuration.getMappedStatement()]
        ↓
[Executor.query()]  ← 一级缓存在此生效
        ↓
[BaseExecutor.createCacheKey()]
        ↓
[是否命中一级缓存?] → 是 → 返回结果
        ↓ 否
[SimpleExecutor.doQuery()]
        ↓
[StatementHandler.prepare()]  ← 获取 Connection + 预编译 SQL
        ↓
[ParameterHandler.setParameters()]  ← 设置 #{} 参数(防注入!)
        ↓
[Statement.execute()]  ← JDBC 执行
        ↓
[ResultSetHandler.handleResultSets()]  ← 封装结果
        ↓
[返回 Java 对象]

🔍 二、关键步骤详解

1️⃣ 入口:调用 Mapper 接口方法

java 复制代码
User user = userMapper.selectById(1L);
  • 本质userMapper 是 MyBatis 通过 JDK 动态代理生成的代理对象。
  • 代理逻辑 :所有方法调用都会进入 MapperProxy.invoke()

💡 为什么能直接调接口?→ MyBatis 在启动时扫描 @Mapper 或 XML,注册了代理工厂。


2️⃣ 路由到 SqlSession

MapperProxy 内部会调用:

java 复制代码
sqlSession.selectOne("com.example.UserMapper.selectById", param);
  • 方法名拼接成 全限定 IDnamespace + "." + method name
  • 这个 ID 对应 XML 中 <select id="selectById">

3️⃣ 获取 MappedStatement

  • Configuration 对象在 MyBatis 启动时就加载了所有 XML/注解,解析成 MappedStatement
  • 每个 SQL 语句(增删改查)都对应一个 MappedStatement,包含:
    • SQL 字符串
    • ResultMap
    • 超时设置
    • 缓存配置等

4️⃣ 执行器(Executor)处理 ------ 一级缓存入口

  • 默认使用 SimpleExecutor(还有 ReuseExecutor, BatchExecutor

  • 关键逻辑

    java 复制代码
    // BaseExecutor.java
    public <E> List<E> query(...) {
      CacheKey key = createCacheKey(ms, param, ...);
      if (localCache.getObject(key) != null) {
        return (List<E>) localCache.getObject(key); // 👈 一级缓存命中
      }
      return queryFromDatabase(...); // 否则查数据库
    }

一级缓存是 SqlSession 级别的 HashMap,只要没 commit/close,相同查询只查一次 DB。


5️⃣ SQL 准备与参数绑定

  • 创建 PreparedStatement(预编译,防 SQL 注入)

  • 通过 ParameterHandler#{id} 替换为 ?,并调用 ps.setLong(1, 1L)

    java 复制代码
    // DefaultParameterHandler
    public void setParameters(PreparedStatement ps) {
      // 遍历参数,调用 TypeHandler 转换并设置
      typeHandler.setParameter(ps, i, value, jdbcType);
    }

🔑 #{} vs ${} 区别就在这里

  • #{} → 预编译参数(安全)
  • ${} → 直接字符串拼接(危险!)

6️⃣ 执行 JDBC 并处理结果

  • 调用 ps.executeQuery()
  • 通过 ResultSetHandler(默认 DefaultResultSetHandler)遍历 ResultSet
  • 根据 ResultMap 或自动映射,将每一行数据转换为 Java 对象
    • 处理 <association> / <collection>
    • 处理驼峰转换(mapUnderscoreToCamelCase

7️⃣ 返回结果

  • 单条 → 返回对象
  • 多条 → 返回 List
  • 插入 → 返回影响行数(或主键,如果配置了 useGeneratedKeys

⚙️ 三、关键组件职责总结

组件 职责
SqlSession 用户操作入口,线程不安全
MapperProxy 动态代理,将接口调用转为 SqlSession 调用
Executor 执行器,负责缓存、事务、真正执行 SQL
StatementHandler 封装 PreparedStatement,处理 SQL 准备
ParameterHandler 设置参数(#{} 绑定)
ResultSetHandler 处理结果集,映射为 Java 对象
TypeHandler Java 类型 ↔ JDBC 类型转换(如 StringVARCHAR

⚠️ 四、大厂关注点(面试加分项)

  1. 一级缓存失效场景

    • 执行了 DML(insert/update/delete)
    • 调用了 clearCache()
    • commit() / close()
  2. 为什么 SqlSession 不是线程安全的?

    → 因为它内部持有 数据库连接 + 一级缓存(HashMap),多线程会冲突。

  3. MyBatis 如何防止 SQL 注入?

    预编译(PreparedStatement) + #{} 参数绑定${} 不安全!


✅ 五、总结

MyBatis 执行流程 = 代理 → 路由 → 缓存 → 预编译 → 执行 → 映射

理解这条链路,你就掌握了 MyBatis 的"脉络",无论是调优、debug 还是面试,都能游刃有余。

如果你正在用 Spring Boot,MyBatis 的 SqlSession 会被 Spring 托管,但底层流程不变。

相关推荐
国科安芯12 小时前
无人驾驶物流车网关的多路CANFD冗余架构与通信可靠性分析
单片机·嵌入式硬件·性能优化·架构·自动驾驶·安全性测试
程序员侠客行12 小时前
Mybatis插件原理及分页插件
java·后端·架构·mybatis
a努力。12 小时前
得物Java面试被问:Netty的ByteBuf引用计数和内存释放
java·开发语言·分布式·python·面试·职场和发展
Mcband12 小时前
Spring Boot 整合 ShedLock 处理定时任务重复执行的问题
java·spring boot·后端
REDcker13 小时前
C86 架构详解
数据库·微服务·架构
大只鹅13 小时前
Java集合框架-Collection
java·开发语言
悟空码字13 小时前
Spring Cloud 集成 Nacos,全面的配置中心与服务发现解决方案
java·nacos·springcloud·编程技术·后端开发
小冷coding13 小时前
【Java】基于Java的线上贷款分发业务技术栈设计方案
java·开发语言
星火开发设计13 小时前
循环结构进阶:while 与 do-while 循环的适用场景
java·开发语言·数据结构·学习·知识·循环