当前位置: 首页 > news >正文

【MyBatis-Plus】源码级吃透 MyBatis 与 MyBatis-Plus:两阶段架构、插件机制与底层增强全解析

作者:大家好,我是 CodeStats。一个在底层技术上“考古”了四年的硬核爱好者,也是 WWAIC(全周项目AI编程)范式的提出者和实践者。我曾手写过一个完整的 Java Web 框架(从 IoC 容器到嵌入式 Tomcat,代码全开源),也喜欢用通俗的语言拆解 CPU、JVM、操作系统的运行本质。

我的技术信条:所有高深的技术,最后都能用大白话讲清楚。如果讲不清楚,说明还没真正理解。


📖 本文收获

  • ✅ 理解 MyBatis 如何封装 JDBC,通过SqlSession完成数据库操作

  • ✅ 掌握 Spring Boot 自动配置 MyBatis 的底层原理

  • ✅ 深入 MyBatis两阶段设计:初始化阶段 + 运行时阶段

  • ✅ 系统学习 MyBatis插件机制,理解分页实现原理

  • ✅ 掌握 MyBatis-Plus 的两阶段增强:MapperRegistryDefaultSqlInjectorMybatisPlusInterceptor

  • ✅ 理清 MP 插件配置与 MyBatis 原生配置的本质区别


📑 目录

  1. MyBatis 如何封装 JDBC 通过 SqlSession 执行?

  2. Spring Boot 如何自动配置 SqlSessionFactory?

  3. MyBatis 两阶段设计的核心组件与执行步骤

  4. MyBatis 插件机制与分页实现原理

  5. MyBatis-Plus 的两阶段增强机制

  6. MP 插件配置与 MyBatis 原生配置的区别


1. MyBatis 如何封装 JDBC 通过 SqlSession 执行?

原生 JDBC 的痛点

java

// 原生 JDBC 七步走 Class.forName("com.mysql.jdbc.Driver"); Connection conn = DriverManager.getConnection(url, username, password); PreparedStatement ps = conn.prepareStatement("SELECT * FROM user WHERE id = ?"); ps.setInt(1, 1); ResultSet rs = ps.executeQuery(); while (rs.next()) { User user = new User(); user.setId(rs.getInt("id")); user.setName(rs.getString("name")); } rs.close(); ps.close(); conn.close();

痛点:硬编码、重复代码、资源管理繁琐、结果映射手动。

MyBatis 的核心封装思路

MyBatis 的本质是对 JDBC 的封装,核心解决四个问题:

  1. 安全执行 SQL,抽象掉 JDBC 的复杂性

  2. 将 Java 参数对象映射到 JDBC PreparedStatement

  3. 将 JDBC ResultSet 的行映射为 Java 对象

  4. 动态生成 SQL(XML 标签或注解)

核心组件体系

组件职责
SqlSessionFactory创建 SqlSession(工厂模式)
SqlSession对外 API,定义 CRUD 方法(门面模式)
ExecutorSQL 执行器,封装 JDBC 操作
StatementHandler处理 Statement,执行 SQL
ParameterHandler设置 SQL 参数
ResultSetHandler将 ResultSet 映射为 Java 对象

SqlSession 执行流程(源码级)

Step 1:获取 SqlSession,创建 Executor

java

// DefaultSqlSessionFactory 源码(简化) public SqlSession openSession() { Executor executor = new SimpleExecutor(transaction, autoCommit); // 关键:通过拦截器链生成代理(插件植入点) executor = (Executor) interceptorChain.pluginAll(executor); return new DefaultSqlSession(configuration, executor, autoCommit); }

Step 2:获取 Mapper 代理对象

java

UserMapper mapper = sqlSession.getMapper(UserMapper.class);

通过MapperRegistryJDK 动态代理生成MapperProxy

Step 3:完整调用链

text

MapperProxy.invoke() → MapperMethod.execute() → SqlSession.selectOne() → 从 Configuration 获取 MappedStatement → Executor.query() → StatementHandler.prepare() → ParameterHandler.setParameters() → Statement.executeQuery() → ResultSetHandler.handleResultSets()

🎯 核心观点

MyBatis 通过SqlSession(门面)+ Executor(执行器)+ StatementHandler(处理器)+ ResultSetHandler(映射器)四层结构,将 JDBC 的复杂性完全封装。开发者只需定义 Mapper 接口和 SQL,框架自动完成参数绑定、SQL 执行和结果映射。


2. Spring Boot 如何自动配置 SqlSessionFactory?

自动配置入口:MybatisAutoConfiguration

Spring Boot 基于META-INF/spring.factories加载MybatisAutoConfiguration

java

@Configuration @ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class}) @ConditionalOnSingleCandidate(DataSource.class) public class MybatisAutoConfiguration { // ... }

关键条件注解

  • @ConditionalOnClass:类路径存在 MyBatis 相关类时才生效

  • @ConditionalOnSingleCandidate(DataSource.class):存在 DataSource Bean 时才生效

SqlSessionFactory 创建流程

Step 1:读取application.yml中的 MyBatis 配置项(mapper-locationsconfig-location等)

Step 2:通过SqlSessionFactoryBean构建(实现了FactoryBean+InitializingBean

java

@Bean @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); factory.setDataSource(dataSource); factory.setMapperLocations(this.properties.getMapperLocations()); return factory.getObject(); }

Step 3buildSqlSessionFactory()内部创建Configuration,解析 XML、注册类型处理器、插件等

SqlSessionTemplate 与 Mapper 扫描

  • SqlSessionTemplate:线程安全,与 Spring 事务管理无缝集成

  • @MapperScan:通过MapperScannerConfigurer将 Mapper 接口注册为MapperFactoryBean

🎯 核心观点

Spring Boot 自动配置 MyBatis 的核心流程:spring.factoriesMybatisAutoConfigurationSqlSessionFactoryBean构建SqlSessionFactory@MapperScan注册 Mapper。开发者只需添加依赖 + 配置数据源,即可无缝使用 MyBatis。


3. MyBatis 两阶段设计的核心组件与执行步骤

MyBatis 的架构清晰划分为两个阶段初始化阶段运行时阶段

3.1 阶段一:初始化阶段(启动时)

目标:解析配置,构建Configuration对象,注册MappedStatement

核心组件职责
SqlSessionFactoryBuilder解析 XML/注解,构建 SqlSessionFactory
Configuration全局配置中心,存放所有 MappedStatement
XMLMapperBuilder解析 Mapper XML 文件
MapperAnnotationBuilder解析 Mapper 接口上的注解
MapperRegistry注册 Mapper 接口与 MapperProxyFactory 的映射

执行步骤

text

1. 读取 mybatis-config.xml 2. 解析 <environments>、<typeAliases>、<plugins> 等配置 3. 扫描 mapper-locations,解析每个 Mapper XML 文件 → XMLMapperBuilder 解析 <select>、<insert> 等标签 → 生成 MappedStatement 并注册到 Configuration.mappedStatements 4. 扫描 Mapper 接口上的 @Select、@Insert 等注解 → MapperAnnotationBuilder 解析并注册 5. 构建 SqlSessionFactory(DefaultSqlSessionFactory)

关键数据结构

java

protected final Map<String, MappedStatement> mappedStatements; // key = "com.example.mapper.UserMapper.selectById" // value = MappedStatement(包含 SQL、参数类型、结果映射等)

3.2 阶段二:运行时阶段(执行时)

目标:执行 SQL,返回结果。

核心组件职责
SqlSession对外 API,接收调用
Executor执行器,调度 SQL 执行
StatementHandler创建 Statement、执行 SQL
ParameterHandler设置 PreparedStatement 参数
ResultSetHandler将 ResultSet 映射为对象

执行步骤

text

1. sqlSession.openSession() → 创建 SqlSession 和 Executor 2. sqlSession.getMapper(XxxMapper.class) → 生成 MapperProxy 3. 调用 Mapper 方法 → MapperProxy 拦截 → 根据 "类名+方法名" 从 Configuration 获取 MappedStatement 4. SqlSession 委派给 Executor 5. Executor 执行:缓存判断 → StatementHandler → ParameterHandler → JDBC 执行 → ResultSetHandler 映射 6. 返回结果

3.3 两阶段的关系

text

┌─────────────────────────────────────────────────────────────┐ │ 初始化阶段(启动时) │ │ 配置文件/注解 → 解析 → MappedStatement → 注册到 Configuration │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 运行时阶段(执行时) │ │ Mapper方法调用 → 查找 MappedStatement → Executor 执行 → 返回结果 │ └─────────────────────────────────────────────────────────────┘

🎯 核心观点

MyBatis 的两阶段设计是框架的基石初始化阶段解析配置、注册 SQL 定义;运行时阶段查找定义、执行 SQL。运行时通过"类名 + 方法名"作为唯一标识查找MappedStatement,两个阶段彻底解耦。


4. MyBatis 插件机制与分页实现原理

4.1 插件机制概述

MyBatis 允许通过插件(Interceptor)对四大核心组件进行拦截:

可拦截组件拦截时机用途示例
ExecutorSQL 执行前后分页、读写分离、性能监控
StatementHandlerSQL 预处理前后SQL 改写、日志记录
ParameterHandler参数设置前后参数加密、默认值填充
ResultSetHandler结果集处理前后结果脱敏、数据权限

插件的本质是 JDK 动态代理,多个插件形成InterceptorChain拦截器链。

4.2 插件执行时机(源码级)

Step 1:创建 Executor 时植入插件

java

// DefaultSqlSessionFactory.openSession() 中 Executor executor = new SimpleExecutor(transaction, autoCommit); // 关键:通过拦截器链生成代理 executor = (Executor) interceptorChain.pluginAll(executor);

pluginAll()遍历所有插件,调用plugin()方法,匹配则返回 JDK 动态代理对象。

Step 2:被代理的方法调用触发intercept()

当调用executor.query()时,实际调用的是代理对象的invoke(),进而触发插件的intercept()

Step 3:拦截器链形成责任链

text

原始 Executor → 插件1代理 → 插件2代理 → 插件3代理 → ...

4.3 分页插件实现原理

分页插件的核心思路:拦截Executor.query(),在 SQL 执行前动态改写 SQL,添加 LIMIT 语句

java

@Intercepts({ @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}) }) public class PagePlugin implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { // 1. 获取原始 SQL String sql = boundSql.getSql(); // 2. 改写 SQL(添加 LIMIT) String pageSql = sql + " LIMIT " + offset + ", " + limit; // 3. 通过反射替换 BoundSql 中的 SQL,继续执行 return invocation.proceed(); } }

4.4 PageHelper 实现思路

  1. 拦截Executor.query(),获取原始 SQL

  2. 生成 COUNT 查询:SELECT COUNT(*) FROM (原SQL)

  3. 执行 COUNT 获取总记录数

  4. 改写原 SQL:添加LIMIT offset, limit(适配数据库方言)

  5. 执行分页查询,将结果和 total 封装成Page对象

🎯 核心观点

MyBatis 插件机制基于JDK 动态代理,在ExecutorStatementHandler等核心组件创建时植入代理。分页插件的本质是在运行时动态改写 SQL,通过拦截Executor.query()添加LIMIT语句并生成 COUNT 查询,实现物理分页。


5. MyBatis-Plus 的两阶段增强机制

MyBatis-Plus 遵循"只做增强,不做改变"的理念,在 MyBatis 两阶段架构上"见缝插针"

5.1 阶段一增强:SQL 注入器(DefaultSqlInjector

目标:在启动时,为BaseMapper的通用 CRUD 方法自动生成并注入MappedStatement

核心组件职责
MybatisMapperRegistry继承MapperRegistry,重写addMapper()
DefaultSqlInjector默认 SQL 注入器,包含 BaseMapper 所有方法的 SQL 模板
AbstractMethod每个 BaseMapper 方法(如selectById)对应的 SQL 生成器
TableInfo实体类元数据(表名、字段名、主键等)

执行流程

text

1. Spring Boot 启动,MybatisPlusAutoConfiguration 生效 2. 扫描 Mapper 接口(如 UserMapper) 3. MybatisMapperRegistry.addMapper(UserMapper.class) → 继承自 MapperRegistry,重写了 addMapper 方法 4. addMapper() 内部调用 DefaultSqlInjector.inspectInject(UserMapper.class) 5. DefaultSqlInjector 获取 BaseMapper 中所有预定义方法列表 6. 遍历每个方法(如 selectById、insert): a. 从 SqlMethod 枚举获取 SQL 模板(如 "SELECT %s FROM %s WHERE %s=#{%s}") b. 通过 TableInfo 获取实体类元数据(表名、字段名等) c. 填充模板,生成完整 SQL d. 创建 MappedStatement,注册到 Configuration

关键示例selectById的 SQL 生成):

java

// SqlMethod 枚举中定义模板 SELECT_BY_ID("selectById", "SELECT %s FROM %s WHERE %s=#{%s}", ...); // 填充后:SELECT id, name, age, email FROM user WHERE id=#{id}

注入时机链路

text

MybatisMapperFactoryBean.checkDaoConfig() → Configuration.addMapper() → MybatisMapperRegistry.addMapper() → DefaultSqlInjector.inspectInject()

5.2 阶段二增强:MybatisPlusInterceptor

目标:在运行时,通过拦截器对 SQL 执行过程进行增强(分页、乐观锁、多租户等)。

核心组件职责
MybatisPlusInterceptorMP 核心拦截器,代理 Executor 和 StatementHandler
InnerInterceptor内部拦截器接口,所有具体插件实现此接口
PaginationInnerInterceptor分页插件
TenantLineInnerInterceptor多租户插件
OptimisticLockerInnerInterceptor乐观锁插件

执行流程

text

1. Mapper 方法被调用(如 userMapper.selectPage(page, wrapper)) 2. MapperProxy → SqlSession → Executor 3. Executor 是被 MybatisPlusInterceptor 代理的对象 4. MybatisPlusInterceptor.intercept() 被触发 5. 遍历内部所有 InnerInterceptor: a. PaginationInnerInterceptor:有分页参数则改写 SQL(添加 COUNT 和 LIMIT) b. TenantLineInnerInterceptor:需要多租户则追加 tenant_id 条件 c. OptimisticLockerInnerInterceptor:有 @Version 则修改 WHERE 条件 6. 所有 InnerInterceptor 执行完毕,调用 invocation.proceed() 执行真实 JDBC 7. 返回增强后的结果(如 Page 对象包含 total + records)

5.3 两阶段增强总结

text

┌─────────────────────────────────────────────────────────────────────┐ │ MyBatis-Plus 两阶段增强 │ ├─────────────────────────────────────────────────────────────────────┤ │ 阶段一(启动时):SQL 注入器(DefaultSqlInjector) │ │ ├── MybatisMapperRegistry.addMapper() 重写 │ │ ├── DefaultSqlInjector.inspectInject() 遍历 BaseMapper 方法 │ │ ├── 从 SqlMethod 获取 SQL 模板,用 TableInfo 填充 │ │ └── 注册 MappedStatement 到 Configuration │ │ 解决的问题:BaseMapper 的 SQL 从哪来? │ ├─────────────────────────────────────────────────────────────────────┤ │ 阶段二(运行时):拦截器(MybatisPlusInterceptor) │ │ ├── 代理 Executor.query() / update() 方法 │ │ ├── 遍历 InnerInterceptor 列表(分页/多租户/乐观锁) │ │ ├── 按需改写 SQL(添加 LIMIT、tenant_id、版本号条件) │ │ └── 执行真实 JDBC 操作 │ │ 解决的问题:分页/租户/乐观锁的 SQL 如何动态改写? │ └─────────────────────────────────────────────────────────────────────┘

🎯 核心观点

MyBatis-Plus 的增强本质上是两阶段各插一脚

  • 阶段一(启动时):通过MybatisMapperRegistry重写addMapper(),用DefaultSqlInjector主动注入BaseMapper的 SQL 定义

  • 阶段二(运行时):通过MybatisPlusInterceptor统一代理,将分页、多租户、乐观锁等增强逻辑以InnerInterceptor形式按需织入SQL 执行过程


6. MP 插件配置与 MyBatis 原生配置的区别

6.1 核心区别

维度MyBatis 原生MyBatis-Plus
插件接口直接实现Interceptor实现InnerInterceptor,由MybatisPlusInterceptor统一代理
注册方式每个插件单独注册到Configuration所有插件添加到MybatisPlusInterceptorinterceptors列表
配置类SqlSessionFactoryBean.setPlugins()MybatisPlusInterceptor.addInnerInterceptor()
拦截粒度拦截四大组件的方法统一拦截后分发给InnerInterceptor

6.2 配置代码对比

MyBatis 原生(Java Config):

java

@Configuration public class MyBatisConfig { @Bean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); factory.setDataSource(dataSource); factory.setPlugins(new Interceptor[]{new PagePlugin()}); return factory.getObject(); } }

MyBatis-Plus(Java Config):

java

@Configuration public class MyBatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(tenantHandler)); interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; } }

6.3 为什么 MP 要重新设计插件配置?

  1. 统一管理:所有增强功能通过一个拦截器管理,避免多个插件互相干扰

  2. 简化使用:开发者只需关注InnerInterceptor的业务逻辑,无需关心 MyBatis 的拦截细节

  3. 顺序可控:通过addInnerInterceptor()的顺序控制插件执行顺序

  4. 更好的扩展性:新增插件只需实现InnerInterceptor

🎯 核心观点

MyBatis-Plus 的插件配置与 MyBatis 原生配置的本质区别在于:从"多个独立插件"演进为"一个统一拦截器 + 多个内部增强器"。MP 通过MybatisPlusInterceptor统一代理,降低了多插件之间的耦合,提供了更简洁、顺序可控的扩展方式,同时完全兼容MyBatis 原生配置。


📝 总结

本文从六个维度深入剖析了 MyBatis 和 MyBatis-Plus 的底层原理:

维度MyBatisMyBatis-Plus
JDBC 封装SqlSession → Executor → StatementHandler → ResultSetHandler完全复用
Spring Boot 配置MybatisAutoConfigurationMybatisPlusAutoConfiguration
两阶段设计启动注册 + 运行执行启动注入 SQL + 运行拦截增强
插件机制4 个拦截点 + 链式调用统一拦截器 + InnerInterceptor
SQL 来源XML/注解静态定义BaseMapper 动态注入

核心思想

MyBatis 的底层本质是"两阶段设计 + 插件机制"

  • 两阶段:启动时注册 SQL 定义,运行时查找并执行

  • 插件:在 Executor 等核心组件创建时植入动态代理

MyBatis-Plus 没有重写 MyBatis,而是在其两阶段架构上"见缝插针"

  • 启动时注入 SQL(DefaultSqlInjector

  • 运行时拦截增强(MybatisPlusInterceptor

理解了这个本质,你就能更好地驾驭这两个框架,遇到问题时也能快速定位是启动阶段还是运行阶段的问题。


如果觉得本文对你有帮助,欢迎点赞、收藏、关注!

你的支持是我持续输出优质技术文章的最大动力

http://www.jsqmd.com/news/1213382/

相关文章:

  • 采购岗位可以考什么证书 - 众智商学院职业教育
  • 使用QEMU搭建ARM开发环境完整指南
  • 亨得利福州区官方维保核验指南|全网正规售后信息权威更新(2026年7月最新) - 亨得利中国服务中心
  • 重庆能源工业技师学院 - 学习招生
  • DFT笔记79
  • 别踩2026年做培训视频总结的常见成本误区,过来人分享实操经验
  • 2026岳阳制冷设备冷库空气能回收公司TOP5测评 - LYL仔仔
  • Cursor写测试用例实战手册(从Chat界面到CI通过的完整链路)
  • 浪琴中国区官方售后服务中心|官网认证地址及电话全新启用(2026年7月最新) - 浪琴中国服务中心
  • Java开发中避免数组越界与空指针异常的实战指南
  • 南京西路梧桐树下的“时光守望”:百年亨得利,镌刻上海海派风华的岁月长歌 - 亨得利官方维修中心
  • LVDS/CSI-2数据流控制:链路列表与FIFO阈值配置实战
  • 西安正规黄金回收门店测评:收的顶资质优势 + 详细地址一览400-867-6661(24小时在线) - 日常比对手册
  • 浪琴重庆官方售后服务中心|官方门店地址及客服电话全新公告(2026年7月最新) - 浪琴中国服务中心
  • 深入解析Windows蓝屏模拟器:从原理到实践的完整开发指南
  • 亨得利重庆维保网点资质攻略|官方公示正规维修服务渠道(2026年7月最新) - 亨得利中国服务中心
  • 浪琴中国区官方售后服务中心|官方网站权威公告(2026年7月最新) - 浪琴中国服务中心
  • 沈阳卖黄金白银铂金别乱找!6 家实体门店可企业长期合作,同步大盘无套路,地址电话全公开 - 不晚生活号
  • DNN Baseline模型
  • 如何用3分钟让你的Royal TSX变成纯中文界面:免费完整汉化指南
  • 产品经理开需求会:2026年4款腾讯会议ai会议纪要哪个更好用?
  • 浪琴深圳官方售后服务网络全指南|最新维修地址及电话权威收录(2026年7月最新) - 浪琴中国服务中心
  • 从零构建AI智能体:全栈开发与实战指南
  • Zpdf OCR:本地化OCR工具部署与批量文字识别实践指南
  • 亨得利中国腕表维保网络全解析|官网核验线下服务网点完整指南(2026 年 7 月最新) - 亨得利中国服务中心
  • 21天学pcie--INTx 的“本质问题”一句话模建议加框)
  • 浪琴全国线下官方售后服务中心|品牌认证技师维保中心权威指南(2026 年 7 月最新) - 浪琴中国服务中心
  • DeepL Chrome翻译插件终极指南:5分钟掌握专业级网页翻译
  • 小红书爬虫技术与图片水印处理实战指南
  • 2026 年 7 月百达翡丽国内官方售后地址 热线统一公示,正规官方维修服务中心查询入口 - 百达翡丽官方服务中心