
简介本资源为MyBatis-Plus官方中文文档离线版面向Java后端开发者、Spring Boot项目工程师及ORM框架学习者旨在解决MyBatis原生开发中SQL重复编写、基础CRUD冗余、条件构造繁琐等效率痛点。文档完整覆盖自动CRUD、主键策略雪花算法/Identity、Lambda表达式查询、QueryWrapper/UpdateWrapper条件构造器、分页插件、公共字段自动填充、乐观锁、数据权限控制及SQL性能分析等核心功能是快速上手与深度实践MP的权威参考。压缩包共140个文件以60个HTML页面含index.html、crud-interface.html、wrapper.html等核心模块和68个JS脚本为主辅以PNG/GIF/SVG等可视化资源与CSS样式文件结构清晰、可本地直接浏览总大小仅3.77MB轻量便携。已有3484人下载学习内容与官网同步更新适合日常查阅、离线备查及团队知识沉淀。1. 别再翻源码找配置项了MyBatis-Plus 官方中文文档不是“说明书”而是你写 CRUD 时的实时决策手册很多 Java 开发者第一次接触 MyBatis-Plus是在同事甩来一句“用 MP 简化 DAO 层”之后。结果打开官网文档发现首页写着“快速开始”点进去却是一堆TableName、LambdaQueryWrapper、IService的零散片段——没有上下文不讲适用边界更没说“为什么这里必须用QueryWrapper而不是LambdaQueryWrapper”。这不是文档缺失而是官方中文文档的定位被严重误读它不是教你怎么敲下第一行mp.insert()的入门教程而是当你在真实项目中面对分页性能抖动、字段自动填充失效、多租户 SQL 拦截异常时能立刻定位到「哪个配置开关控制行为」「哪段注解决定执行路径」「哪个参数影响 SQL 渲染逻辑」的精准索引系统。它服务的对象不是刚学完 JDBC 的新人而是正在调试updateById返回 0 却查不到日志的中级开发者是需要在 Spring Boot 3.x JDK 17 环境下复用旧版MetaObjectHandler的迁移者是必须把TableField(fill FieldFill.INSERT_UPDATE)和Version同时生效的业务建模者。本文不重讲“什么是 ORM”只带你把官方中文文档真正用起来从结构设计逻辑出发还原每个模块的决策链路给出可粘贴验证的最小可运行配置并标注所有你在application.yml或Configuration中实际会修改的参数及其副作用。2. 官方中文文档的三层结构解析为什么“快速开始”之后要直奔“配置项”和“核心功能”官方中文文档表面是线性阅读流实则按“能力分层”组织。跳过结构直接查 API就像拿着菜谱找灶台开关——找不到关键控制点。真正高效的用法是先理解其三层骨架基础支撑层配置与启动→ 核心能力层CRUD/条件构造/分页→ 扩展治理层插件/自动填充/多租户。这三层对应文档中三个最常被跳过的章节“配置项说明”、“核心功能”、“插件扩展”。新手常卡在“为什么selectList(wrapper)查不到数据”本质是没意识到wrapper的构建方式受configuration.mapUnderscoreToCamelCase和global-config.db-config.id-type双重影响老手调优时纠结“分页 count 查询太慢”却忘了mybatis-plus.configuration.default-fetch-size和pagination.interceptor.count-sql是两个独立开关。下面拆解这三层如何联动并给出每个层级你必须掌握的 3 个关键入口。2.1 基础支撑层mybatis-plus配置块不是可选的而是行为定义的源头MyBatis-Plus 的行为不像纯 MyBatis 那样由 XML 和SqlSessionFactoryBean主导而是由MybatisPlusAutoConfiguration自动装配其核心是MybatisPlusProperties类。这个类将所有配置映射为mybatis-plus.*前缀的属性而这些属性直接决定 SQL 解析器、主键生成策略、甚至日志输出格式。例如# application.yml mybatis-plus: configuration: # 关键开启驼峰转换否则 user_name 字段无法映射到 userName map-underscore-to-camel-case: true # 关键设置默认 fetch size避免大数据量分页时内存溢出 default-fetch-size: 100 global-config: db-config: # 关键主键类型决定 insert 时是否自动生成 IDID_WORKER 不等于 UUID id-type: assign_id # 关键表名前缀配合 TableName(value user) 实现动态前缀 table-prefix: t_提示id-type: assign_id表示使用雪花算法生成 Long 型 ID而非数据库自增。若实体类id字段是String类型必须改为id-type: assign_uuid否则启动报错Can not find method getId in class xxx。这是文档“配置项说明”中“全局配置”小节里最易忽略的类型约束。2.2 核心功能层QueryWrapper和LambdaQueryWrapper的选择不是语法糖而是编译期安全与运行时灵活性的权衡文档“核心功能”章节列出 6 种 Wrapper但日常开发只需掌握两种QueryWrapper字符串字段名和LambdaQueryWrapper方法引用。它们的区别远不止“是否类型安全”QueryWrapper支持动态字段拼接如wrapper.eq(status_ tenantId, 1)适合多租户字段后缀场景LambdaQueryWrapper在编译期校验字段存在性但无法处理ORDER BY FIELD(id, 3,1,2)这类数据库函数排序。验证二者差异的最小代码// 使用 QueryWrapper字段名硬编码IDE 不提示错误运行时报 Unknown column user_name QueryWrapperUser qw1 new QueryWrapper(); qw1.eq(user_name, zhangsan); // ✅ 正确但字段名易写错 // 使用 LambdaQueryWrapper字段名由方法引用保证IDE 实时提示 LambdaQueryWrapperUser lwq new LambdaQueryWrapper(); lwq.eq(User::getUserName, zhangsan); // ✅ 编译通过即字段存在 // 但以下需求只能用 QueryWrapper QueryWrapperUser qw2 new QueryWrapper(); qw2.orderBy(true, true, FIELD(id, String.join(,, ids) )); // ✅ 动态 IN 排序注意LambdaQueryWrapper的eq方法底层仍会将User::getUserName解析为user_name字段名所以map-underscore-to-camel-case: true必须开启否则解析结果为userName导致 SQL 报错。这是配置层与功能层的强耦合文档未明说但调试时必踩。2.3 扩展治理层插件不是“加功能”而是 SQL 生命周期的切面控制点“插件扩展”章节列出了分页、性能分析、多租户等插件但新手常误以为“引入依赖加Bean就完事”。实际上每个插件都注册在Interceptor链中其执行顺序直接影响结果。以分页插件为例官方文档强调PaginationInnerInterceptor但没说明它必须在MybatisPlusAutoConfiguration初始化后才生效Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); // 关键分页插件必须第一个注册否则 count 查询可能被其他插件拦截 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 关键多租户插件必须在分页之后否则分页 SQL 的 WHERE 条件会被租户条件覆盖 interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() { Override public Expression getTenantId() { return new LongValue(1L); // 实际从 ThreadLocal 获取 } Override public String getTenantIdColumn() { return tenant_id; } })); return interceptor; } }提示PaginationInnerInterceptor的DbType.MYSQL参数决定分页方言若项目同时连接 MySQL 和 PostgreSQL需配置DynamicTableNameParser或自定义IPage实现。文档中“分页插件”小节只提了单库场景这是多数据源项目的真实坑点。3. 从文档“常见问题”反推配置陷阱5 个高频报错的根因与修复命令官方文档“常见问题”章节只有 8 个条目但覆盖了 70% 的线上故障。这些条目不是罗列现象而是暴露了配置、注解、SQL 解析三者的隐式依赖关系。下面选取 5 个最具代表性的案例给出可立即执行的诊断命令和修复配置。3.1 报错org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)不是 Mapper XML 缺失而是扫描路径未生效现象UserMapper.selectList(null)报错但UserMapper.xml存在且 namespace 正确。根因MyBatis-Plus 的MapperScan注解未覆盖到 Mapper 接口包或mapper-locations配置路径错误。验证命令Spring Boot Actuator 端点curl http://localhost:8080/actuator/mappings | grep UserMapper # 若无返回说明 Mapper 未被扫描修复配置二选一# 方案1用 MapperScan 注解推荐 SpringBootApplication MapperScan(com.example.mapper) # 显式指定包路径 public class Application { ... } # 方案2用 yml 配置需确保路径正确 mybatis-plus: mapper-locations: classpath*:mapper/**/*Mapper.xml # 注意 *Mapper.xml 后缀 type-aliases-package: com.example.entity注意mapper-locations的classpath*:表示扫描所有 jar 包中的 mapper 文件而classpath:只扫描当前模块。微服务架构下必须用classpath*:否则依赖的公共 mapper 包无法加载。3.2updateById返回 0 但数据库有数据不是 SQL 错误而是乐观锁版本号未更新现象user.setVersion(1); userMapper.updateById(user)返回 0查数据库发现 version 字段仍是 1。根因Version注解字段未参与 SQL 更新或optimisticLockerInnerInterceptor未注册。验证步骤// 检查实体类是否正确定义 Version Data public class User { TableId private Long id; private String name; Version // ✅ 必须有此注解 private Integer version; // ✅ 类型必须是 Integer/Long不能是 int/long }修复配置Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; }提示Version字段在updateById时会自动添加WHERE version #{version}条件。若数据库 version 是 2而传入对象 version 是 1则 SQL 影响行数为 0这是预期行为不是 bug。3.3 分页查询count结果为 0不是数据问题而是IPage泛型未指定现象IPageUser page userMapper.selectPage(new Page(1,10), wrapper)返回page.getRecords()有数据但page.getTotal()为 0。根因IPage未指定泛型导致PaginationInnerInterceptor无法识别返回类型跳过 count 查询。错误写法IPage page userMapper.selectPage(new Page(1,10), wrapper); // ❌ 无泛型正确写法IPageUser page userMapper.selectPage(new Page(1,10), wrapper); // ✅ 有泛型注意Page构造函数参数是(current, size)不是(offset, limit)。new Page(1,10)表示第 1 页每页 10 条若写成new Page(0,10)则 current0分页插件会认为无需分页直接返回全部数据。3.4TableField(fill FieldFill.INSERT)字段未填充不是注解失效而是MetaObjectHandler未被调用现象插入新记录时create_time字段为 null。根因MetaObjectHandler实现类未被 Spring 扫描或strict模式下字段名不匹配。验证命令# 查看 Spring 容器中是否注册了 MetaObjectHandler Bean curl http://localhost:8080/actuator/beans | grep metaObjectHandler # 应返回类似 metaObjectHandler: { bean:metaObjectHandler, scope:singleton }修复代码Component // ✅ 必须加 Component 让 Spring 管理 public class MyMetaObjectHandler implements MetaObjectHandler { Override public void insertFill(MetaObject metaObject) { // 关键字段名必须与数据库列名一致非驼峰因为 fill 发生在 SQL 解析前 this.strictInsertFill(metaObject, create_time, LocalDateTime.class, LocalDateTime.now()); // 起始字段名 } Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, update_time, LocalDateTime.class, LocalDateTime.now()); } }提示strictInsertFill的第一个参数是数据库列名如create_time不是 Java 字段名如createTime。若实体类用TableField(create_time)显式指定则此处必须用create_time。3.5 多租户插件未生效不是插件未启用而是TenantLineHandler的ignoreTable未排除系统表现象user表的 SQL 自动添加AND tenant_id 1但sys_log表也加了该条件导致查询失败。根因TenantLineHandler默认对所有表生效需显式声明忽略表。修复代码interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() { Override public Expression getTenantId() { return new LongValue(TenantContext.getTenantId()); } Override public String getTenantIdColumn() { return tenant_id; } Override public boolean ignoreTable(String tableName) { // ✅ 显式忽略 sys_ 开头的系统表 return tableName.startsWith(sys_); } }));注意ignoreTable方法在每次 SQL 解析时调用若逻辑复杂如查数据库判断会导致性能下降。应只做简单字符串匹配。4. 文档未写的实战技巧用InterceptorIgnore绕过插件的 3 种精确控制方式官方文档“插件扩展”章节只讲了全局启用但真实项目中常需“局部禁用”——比如分页插件不应作用于导出接口多租户插件不应过滤定时任务的job_log表。InterceptorIgnore注解就是为此设计的精准开关但它有 3 种粒度文档未说明使用场景。4.1 方法级忽略针对特定 Mapper 方法禁用分页当某个导出接口需查全量数据但又不想写原生 SQL 时Mapper public interface UserMapper extends BaseMapperUser { // ✅ 禁用分页插件但保留乐观锁、多租户 InterceptorIgnore(page true) ListUser selectAllForExport(); // ✅ 同时禁用分页和多租户 InterceptorIgnore(page true, tenantLine true) ListUser selectAllForAdmin(); }4.2 SQL 片段级忽略在 Wrapper 中动态控制插件行为当需要对同一方法的不同调用启用不同插件时// 查询租户内用户启用多租户 LambdaQueryWrapperUser tenantQw new LambdaQueryWrapper(); tenantQw.eq(User::getStatus, 1); ListUser tenantUsers userMapper.selectList(tenantQw); // 查询所有用户禁用多租户 LambdaQueryWrapperUser allQw new LambdaQueryWrapper(); allQw.eq(User::getStatus, 1); // ✅ 在 Wrapper 中设置 ignore 属性 allQw.setEntity(new User().setTenantId(null)); // 触发 tenantLine 插件忽略逻辑 // 或更直接的方式 allQw.last( /*%mybatis-plus-ignored:tenantLine%*/ ); // 注释方式忽略 ListUser allUsers userMapper.selectList(allQw);4.3 全局配置级忽略用InterceptorIgnoreProperty统一管理忽略规则当项目有大量需忽略的表且规则固定时避免在每个方法加注解Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // ✅ 全局配置对 sys_、qrtz_ 开头的表自动忽略分页和多租户 InterceptorIgnoreProperty ignoreProperty new InterceptorIgnoreProperty(); ignoreProperty.setPage(Arrays.asList(sys_%, qrtz_%)); ignoreProperty.setTenantLine(Arrays.asList(sys_%, qrtz_%)); interceptor.setInterceptorIgnoreProperty(ignoreProperty); return interceptor; } }提示InterceptorIgnoreProperty的page和tenantLine是ListString支持LIKE通配符%和精确匹配。sys_%匹配sys_user、sys_role但不匹配system_log。5. 验证文档配置是否生效的 4 个终端命令不用重启实时观测 SQL 行为改完application.yml或Configuration别急着重启应用。用以下命令直接观测配置是否被加载、插件是否注册、SQL 是否被改写——这是高效使用官方中文文档的最后一步。5.1 查看所有 MyBatis-Plus 配置属性是否加载成功# Spring Boot 2.x curl http://localhost:8080/actuator/configprops?includemybatis-plus # Spring Boot 3.x需启用 endpoint curl http://localhost:8080/actuator/configprops?includemybatis-plus返回 JSON 中应包含mybatis-plus-com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties: { prefix: mybatis-plus, properties: { configuration: { map-underscore-to-camel-case: true }, global-config: { db-config: { id-type: assign_id } } } }5.2 查看已注册的 Interceptor 链顺序curl http://localhost:8080/actuator/beans | grep -A 10 mybatisPlusInterceptor输出应类似mybatisPlusInterceptor: { aliases: [], scope: singleton, type: com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor, resource: class path resource [com/example/config/MybatisPlusConfig.class], dependencies: [] }5.3 开启 SQL 日志验证分页插件是否生成 count 查询# application.yml 临时开启 logging: level: com.baomidou.mybatisplus.extension.plugins.pagination: debug com.example.mapper.UserMapper: debug调用userMapper.selectPage(new Page(1,10), wrapper)后日志中应出现 Preparing: SELECT COUNT(*) FROM user WHERE status ? Parameters: 1(Integer) Total: 1 Preparing: SELECT id,name,create_time FROM user WHERE status ? LIMIT ? Parameters: 1(Integer), 10(Long)5.4 验证TableField(fill)是否触发填充逻辑在MyMetaObjectHandler中加日志Override public void insertFill(MetaObject metaObject) { log.info(insertFill triggered for: {}, metaObject.getOriginalObject().getClass().getSimpleName()); this.strictInsertFill(metaObject, create_time, LocalDateTime.class, LocalDateTime.now()); }执行userMapper.insert(new User().setName(test))日志应输出insertFill triggered for User。若无输出说明MetaObjectHandler未被调用需检查Component和包扫描路径。注意TableField(fill FieldFill.INSERT)的填充发生在insert方法内部不会作用于insertBatchSomeColumn等批量方法。这是文档未明确说明的边界但生产环境必须知晓。本文还有配套的精品资源点击获取