谈谈 Mybatis 的工作原理?

一则或许对你有用的小广告

欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于 Spring AI + Spring Boot3.x + JDK 21...点击查看;
  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot3.x + JDK 17...点击查看项目介绍; 演示链接: http://116.62.199.48:7070/;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/

面试考察点

面试官提出这个问题,通常希望考察以下几个层面,而不仅仅是知道一个简单的流程:

  1. 对 MyBatis 整体架构的理解:能否宏观描述 MyBatis 如何将 JDBC 操作封装、简化,核心组件有哪些以及它们如何协作。
  2. 对核心工作原理的掌握深度:特别是 SqlSessionExecutorMappedStatement 这几个核心对象在 一次数据库操作 中的职责与调用链。
  3. 对动态代理机制的应用:是否理解 MyBatis 如何通过动态代理,将我们定义的 Mapper 接口XML/注解配置的 SQL 绑定起来,这是其设计精髓。
  4. 对配置解析和缓存机制的认识:了解配置文件的加载过程以及一级/二级缓存的工作位置和生命周期,这是性能优化和排查问题的基础。

核心答案

MyBatis 的工作原理可以概括为:通过封装 JDBC,提供了一套以 SqlSession 为核心,基于接口绑定和动态代理的数据库操作框架

一次典型的查询操作,其核心流程如下:

  1. 加载配置:启动时解析 mybatis-config.xml 和所有 Mapper.xml 文件,将配置信息(数据源、事务、SQL 语句等)构建成内存中的配置对象 Configuration
  2. 创建 SqlSessionFactory:用上一步的 Configuration 对象创建 SqlSessionFactory,它是生产 SqlSession 的工厂。
  3. 创建 SqlSession:通过 SqlSessionFactory 打开一个 SqlSession,它代表一次数据库会话。
  4. 获取 Mapper 代理对象:调用 sqlSession.getMapper(MapperInterface.class),MyBatis 会使用 动态代理 技术,为该接口生成一个代理对象。
  5. 执行数据库操作:调用代理对象的方法时,代理逻辑会将 方法名参数 转换为对应的 MappedStatement ID,并委托给 SqlSession 执行。SqlSession 内部通过 Executor(执行器)来操作数据库。Executor 会先查询缓存,然后通过 StatementHandler 编译 SQL、设置参数,最终通过底层的 JDBC 执行 SQL 并获取结果。
  6. 结果映射ResultSetHandler 将 JDBC 返回的 ResultSet 转换为 Java 对象(根据映射配置),并返回。
  7. 关闭会话:最终关闭 SqlSession

深度解析

原理/机制

  • 配置初始化:所有 XML 和注解配置最终被解析为 Configuration 单例对象,它是 MyBatis 的 “大脑”。
  • 动态代理绑定:这是关键。Mapper 接口本身没有实现类。MyBatis 利用 JDK 动态代理(或 CGLIB),为接口生成代理对象。代理类的 invoke 方法会拦截所有接口方法的调用,并将调用转发给 SqlSessionselectinsertupdatedelete 等方法。方法名和命名空间共同决定了要执行哪条 SQL。
  • 执行器分层Executor 是执行核心,采用职责链模式。主要有三种:SimpleExecutor(简单执行,每次新建 Statement)、ReuseExecutor(重用 Statement)、BatchExecutor(批处理)。它也是一级缓存Local Cache)的存放地,其生命周期与 SqlSession 相同。
  • 二级缓存:基于 Mapper 命名空间,存储在 Configuration 对象中,生命周期与应用同步。需显式开启,并由 CachingExecutor 装饰器代理基础执行器来实现。

最佳实践与常见误区

  • #{}${}务必理解 #{} 是预编译参数占位符(防止 SQL 注入),${} 是字符串替换(有注入风险,常用于动态表名/列名)。
  • 接口与 XML 的绑定:方法名需与 XML 中 statementid 一致,返回值类型、参数类型需匹配。
  • 一级缓存:在同一个 SqlSession 中,相同的查询会直接从缓存返回。但 任何 UPDATE 操作都会清空该 SqlSession 的一级缓存,这可能导致在事务中重复查询看不到最新数据(需理解该机制)。
  • 二级缓存:分布式环境下,默认的基于 Map 的二级缓存会导致数据不一致,生产环境通常使用 Redis 等集中式缓存替代,或直接关闭二级缓存。

总结

MyBatis 的核心原理是 利用动态代理将接口调用转化为对 SqlSession 模板方法的调用,并通过 ExecutorStatementHandler 等组件链式完成 SQL 准备、参数绑定、执行及结果映射,其设计精巧地平衡了灵活性(手写 SQL)、易用性(接口化)与可扩展性(插件机制)。