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

相关推荐
Mcband12 小时前
Java 三方 JSON 比对
java·开发语言·json
wanghowie12 小时前
02.04.02 Reactor 实战教程:响应式编程从入门到精通
java·reactor
出门撞大运12 小时前
HashMap详解
java
青云交12 小时前
Java 大视界 -- 实战|Elasticsearch+Java 电商搜索系统:分词优化与千万级 QPS 性能调优(439)
java·spring boot·elasticsearch·性能优化·搜索系统·容器化部署·母婴电商
Wang153012 小时前
Java的面向对象
java
!chen12 小时前
Spring Boot Pf4j模块化开发
java·spring boot·spring
无人装备硬件开发爱好者12 小时前
AI 辅助程序设计的趋势与范式转移:编码、审核、测试全流程深度解析
大数据·人工智能·架构·核心竞争力重构
趁月色小酌***12 小时前
吃透Java核心:从基础语法到并发编程的实战总结
java·开发语言·python
计算机毕设指导612 小时前
基于Django的本地健康宝微信小程序系统【源码文末联系】
java·后端·python·mysql·微信小程序·小程序·django
Ccuno12 小时前
Java中常用的数据结构实现类概念
java·开发语言·深度学习