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

相关推荐
天天被压力4 分钟前
【跨市场数据实战 #08】可转债折价机会怎么筛:3个接口抓比价、列表和实时盘口
java·人工智能·python
一尘之中8 分钟前
指挥控制中心与指控系统:概念、应用、厂商与DDS技术全景
学习·架构·ai写作
张某布响丸辣1 小时前
附件下载的安全边界:路径白名单、NAS 哨兵文件与 Redis Lua 一次性令牌
java·redis·redis lua·nas哨兵
徐小夕1 小时前
存量.doc 文档怎么办?JitWord 开源转换库打通旧文档到在线协同全链路
后端·架构·github
安易算力1 小时前
PUE优化工程实践:从1.5到1.2的制冷架构与气流组织改造路径
网络·python·容器·架构·kubernetes
YatHinLay1 小时前
Spring Boot + Calcite 实现跨库查询
java
酷炫码神2 小时前
MosMos 创意效果与能力边界展示
java
这个DBA有点耶2 小时前
InnoDB索引组织表下,复合主键和自增主键的物理存储差异与选型对比
数据库·mysql·架构
Wang's Blog2 小时前
Java 接入Redis: 使用Jedis操作Redis
java·服务器·redis
事圆则缓2 小时前
Java 8 Lambda、Stream、Optional 与 Android 边界:从回调语法到运行时兼容
android·java