(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 托管,但底层流程不变。

相关推荐
Tan_Ying_Y2 小时前
JVM内存结构,什么是栈桢?
java·jvm
木井巳2 小时前
【多线程】Thread类及常用方法
java·java-ee
小年糕是糕手2 小时前
【C++】内存管理(下)
java·c语言·开发语言·数据结构·c++·算法
CoderYanger2 小时前
第 479 场周赛Q2——3770. 可表示为连续质数和的最大质数
java·数据结构·算法·leetcode·职场和发展
L.EscaRC2 小时前
Spring Boot开发中加密数据的模糊搜索
java·spring boot·后端
艾莉丝努力练剑2 小时前
【Linux基础开发工具 (六)】Linux中的第一个系统程序——进度条Linux:详解回车、换行与缓冲区
java·linux·运维·服务器·c++·centos
8Qi82 小时前
Redis之Lua脚本与分布式锁改造
java·redis·分布式·lua
钱多多_qdd2 小时前
mini-spring基础篇:IoC(十一):Aware接口
java·spring