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

发布时间:2026/7/18 13:18:36
【MyBatis-Plus】源码级吃透 MyBatis 与 MyBatis-Plus:两阶段架构、插件机制与底层增强全解析 作者大家好我是 CodeStats。一个在底层技术上“考古”了四年的硬核爱好者也是 WWAIC全周项目AI编程范式的提出者和实践者。我曾手写过一个完整的 Java Web 框架从 IoC 容器到嵌入式 Tomcat代码全开源也喜欢用通俗的语言拆解 CPU、JVM、操作系统的运行本质。我的技术信条所有高深的技术最后都能用大白话讲清楚。如果讲不清楚说明还没真正理解。 本文收获✅ 理解 MyBatis 如何封装 JDBC通过SqlSession完成数据库操作✅ 掌握 Spring Boot 自动配置 MyBatis 的底层原理✅ 深入 MyBatis两阶段设计初始化阶段 运行时阶段✅ 系统学习 MyBatis插件机制理解分页实现原理✅ 掌握 MyBatis-Plus 的两阶段增强MapperRegistry、DefaultSqlInjector、MybatisPlusInterceptor✅ 理清 MP 插件配置与 MyBatis 原生配置的本质区别 目录MyBatis 如何封装 JDBC 通过 SqlSession 执行Spring Boot 如何自动配置 SqlSessionFactoryMyBatis 两阶段设计的核心组件与执行步骤MyBatis 插件机制与分页实现原理MyBatis-Plus 的两阶段增强机制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 的封装核心解决四个问题安全执行 SQL抽象掉 JDBC 的复杂性将 Java 参数对象映射到 JDBC PreparedStatement将 JDBC ResultSet 的行映射为 Java 对象动态生成 SQLXML 标签或注解核心组件体系组件职责SqlSessionFactory创建 SqlSession工厂模式SqlSession对外 API定义 CRUD 方法门面模式ExecutorSQL 执行器封装 JDBC 操作StatementHandler处理 Statement执行 SQLParameterHandler设置 SQL 参数ResultSetHandler将 ResultSet 映射为 Java 对象SqlSession 执行流程源码级Step 1获取 SqlSession创建 Executorjava// DefaultSqlSessionFactory 源码简化 public SqlSession openSession() { Executor executor new SimpleExecutor(transaction, autoCommit); // 关键通过拦截器链生成代理插件植入点 executor (Executor) interceptorChain.pluginAll(executor); return new DefaultSqlSession(configuration, executor, autoCommit); }Step 2获取 Mapper 代理对象javaUserMapper mapper sqlSession.getMapper(UserMapper.class);通过MapperRegistry和JDK 动态代理生成MapperProxy。Step 3完整调用链textMapperProxy.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自动配置入口MybatisAutoConfigurationSpring Boot 基于META-INF/spring.factories加载MybatisAutoConfigurationjavaConfiguration 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-locations、config-location等Step 2通过SqlSessionFactoryBean构建实现了FactoryBeanInitializingBeanjavaBean 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.factories→MybatisAutoConfiguration→SqlSessionFactoryBean构建SqlSessionFactory→MapperScan注册 Mapper。开发者只需添加依赖 配置数据源即可无缝使用 MyBatis。3. MyBatis 两阶段设计的核心组件与执行步骤MyBatis 的架构清晰划分为两个阶段初始化阶段和运行时阶段。3.1 阶段一初始化阶段启动时目标解析配置构建Configuration对象注册MappedStatement。核心组件职责SqlSessionFactoryBuilder解析 XML/注解构建 SqlSessionFactoryConfiguration全局配置中心存放所有 MappedStatementXMLMapperBuilder解析 Mapper XML 文件MapperAnnotationBuilder解析 Mapper 接口上的注解MapperRegistry注册 Mapper 接口与 MapperProxyFactory 的映射执行步骤text1. 读取 mybatis-config.xml 2. 解析 environments、typeAliases、plugins 等配置 3. 扫描 mapper-locations解析每个 Mapper XML 文件 → XMLMapperBuilder 解析 select、insert 等标签 → 生成 MappedStatement 并注册到 Configuration.mappedStatements 4. 扫描 Mapper 接口上的 Select、Insert 等注解 → MapperAnnotationBuilder 解析并注册 5. 构建 SqlSessionFactoryDefaultSqlSessionFactory关键数据结构javaprotected final MapString, MappedStatement mappedStatements; // key com.example.mapper.UserMapper.selectById // value MappedStatement包含 SQL、参数类型、结果映射等3.2 阶段二运行时阶段执行时目标执行 SQL返回结果。核心组件职责SqlSession对外 API接收调用Executor执行器调度 SQL 执行StatementHandler创建 Statement、执行 SQLParameterHandler设置 PreparedStatement 参数ResultSetHandler将 ResultSet 映射为对象执行步骤text1. 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 语句。javaIntercepts({ 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 实现思路拦截Executor.query()获取原始 SQL生成 COUNT 查询SELECT COUNT(*) FROM (原SQL)执行 COUNT 获取总记录数改写原 SQL添加LIMIT offset, limit适配数据库方言执行分页查询将结果和 total 封装成Page对象 核心观点MyBatis 插件机制基于JDK 动态代理在Executor、StatementHandler等核心组件创建时植入代理。分页插件的本质是在运行时动态改写 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实体类元数据表名、字段名、主键等执行流程text1. 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}注入时机链路textMybatisMapperFactoryBean.checkDaoConfig() → Configuration.addMapper() → MybatisMapperRegistry.addMapper() → DefaultSqlInjector.inspectInject()5.2 阶段二增强MybatisPlusInterceptor目标在运行时通过拦截器对 SQL 执行过程进行增强分页、乐观锁、多租户等。核心组件职责MybatisPlusInterceptorMP 核心拦截器代理 Executor 和 StatementHandlerInnerInterceptor内部拦截器接口所有具体插件实现此接口PaginationInnerInterceptor分页插件TenantLineInnerInterceptor多租户插件OptimisticLockerInnerInterceptor乐观锁插件执行流程text1. 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 records5.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所有插件添加到MybatisPlusInterceptor的interceptors列表配置类SqlSessionFactoryBean.setPlugins()MybatisPlusInterceptor.addInnerInterceptor()拦截粒度拦截四大组件的方法统一拦截后分发给InnerInterceptor6.2 配置代码对比MyBatis 原生Java ConfigjavaConfiguration 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-PlusJava ConfigjavaConfiguration 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 要重新设计插件配置统一管理所有增强功能通过一个拦截器管理避免多个插件互相干扰简化使用开发者只需关注InnerInterceptor的业务逻辑无需关心 MyBatis 的拦截细节顺序可控通过addInnerInterceptor()的顺序控制插件执行顺序更好的扩展性新增插件只需实现InnerInterceptor 核心观点MyBatis-Plus 的插件配置与 MyBatis 原生配置的本质区别在于从多个独立插件演进为一个统一拦截器 多个内部增强器。MP 通过MybatisPlusInterceptor统一代理降低了多插件之间的耦合提供了更简洁、顺序可控的扩展方式同时完全兼容MyBatis 原生配置。 总结本文从六个维度深入剖析了 MyBatis 和 MyBatis-Plus 的底层原理维度MyBatisMyBatis-PlusJDBC 封装SqlSession → Executor → StatementHandler → ResultSetHandler完全复用Spring Boot 配置MybatisAutoConfigurationMybatisPlusAutoConfiguration两阶段设计启动注册 运行执行启动注入 SQL 运行拦截增强插件机制4 个拦截点 链式调用统一拦截器 InnerInterceptorSQL 来源XML/注解静态定义BaseMapper 动态注入核心思想MyBatis 的底层本质是两阶段设计 插件机制两阶段启动时注册 SQL 定义运行时查找并执行插件在 Executor 等核心组件创建时植入动态代理MyBatis-Plus 没有重写 MyBatis而是在其两阶段架构上见缝插针启动时注入 SQLDefaultSqlInjector运行时拦截增强MybatisPlusInterceptor理解了这个本质你就能更好地驾驭这两个框架遇到问题时也能快速定位是启动阶段还是运行阶段的问题。如果觉得本文对你有帮助欢迎点赞、收藏、关注你的支持是我持续输出优质技术文章的最大动力