
前言日常Java后台开发中绝大多数业务都是单表增删改查。原生MyBatis虽然灵活但存在大量重复劳动每张表都要手写insert、update、select、delete的XML语句动态条件拼接需要手动处理字符串判空分页、时间戳、软删除等通用能力需要自己封装工具类开发效率低且重复代码难以维护。本文带你从零入门MyBatis-Plus理清核心能力与基础用法适合新手快速上手。一、背景MyBatis-Plus 解决了什么问题1. MyBatis 的痛点1. 单表CRUD必须手动编写XML/注解SQL实体越多重复代码越多2. 动态查询条件需要手动拼接字符串极易出现SQL注入、括号逻辑错乱3. 分页、逻辑删除、创建更新时间、并发乐观锁、分布式主键等通用功能无内置实现每个项目重复封装4. 实体与数据库字段映射、多条件查询样板代码冗余。2. MyBatis-Plus简称MP定位MP 是「MyBatis 的增强工具只增强、不修改」完全兼容原生MyBatis所有能力底层依旧依赖MyBatis执行SQL。核心目标「消除单表CRUD冗余代码内置企业级通用持久层能力大幅提升后台开发效率」。3. 核心优势- 无侵入原有MyBatis XML、自定义SQL完全保留可平滑迁移- 开箱即用CRUD、分页、条件构造器无需手写SQL- 内置通用语法糖逻辑删除、自动填充、乐观锁、分布式主键- Lambda语法构造条件编译校验字段杜绝字符串硬编码错误。适用场景管理后台、CRUD为主业务、中小型服务复杂多表联查、统计报表依旧可以手写原生MyBatis XML。二、MyBatis-Plus 核心增强功能2.1 强大CRUD封装BaseMapper IService 双层持久层MP 提供两套分层CRUD封装分别对应DAO层与Service层职责清晰。1DAO层BaseMapperMapper接口继承所有Mapper接口继承BaseMapperT无需编写任何方法自动拥有单表基础操作能力直接操作数据库。// Mapper接口无需自定义CRUD方法 Mapper public interface UserMapper extends BaseMapperUser { }常用内置方法-insert(T entity)新增单条数据-selectById(id)、deleteById(id)、updateById(entity)根据主键查询/删除/更新-selectList(Wrapper)、delete(Wrapper)按条件批量查询、批量删除-selectPage(Page, Wrapper)分页查询。-「自定义SQL方法」开发者可在Mapper接口中自定义任意抽象方法配合XML/注解编写专属SQL和BaseMapper自带方法共存使用。2Service层IService ServiceImpl业务层封装业务层做二次封装在BaseMapper基础上新增批量操作、链式查询、语义化方法规范业务代码分层。- 接口继承IServiceT- 实现类继承ServiceImplM,T内置注入Mapper封装通用逻辑。// Service接口 public interface UserService extends IServiceUser { } // Service实现类 Service public class UserServiceImpl extends ServiceImplUserMapper, User implements UserService { }IService独有增强能力1. 批量操作saveBatch批量新增、updateBatchById批量更新2. 链式Lambda查询lambdaQuery().eq().list()极简写法3. 语义化方法getById、list、page区分DAO层底层方法业务可读性更强。4.「主键批量操作高频实用」IService 内置开箱即用批量主键操作方法无需手动拼接 IN 条件底层优化批量SQL性能listByIds(CollectionID idList)根据主键批量查询多条数据removeByIds(CollectionID idList)根据主键批量删除数据updateBatchById(CollectionT entityList)批量更新多条数据saveBatch(CollectionT entityList)批量新增数据// 批量查询 ListLong idList Arrays.asList(1L,2L,3L); ListUser userList userService.listByIds(idList); // 批量删除 userService.removeByIds(idList);两者区别总结类层级核心特点使用场景BaseMapperMapper/DAO底层原生数据库操作无批量封装简单单条操作、自定义Mapper内部调用IServiceService业务层封装批量、链式查询、事务友好项目标准分层开发Controller直接调用2.2 条件构造器 Wrapper动态拼接Where条件重点完整版Wrapper 是 MyBatis-Plus 最核心的语法糖「彻底解决原生MyBatis动态SQL繁琐拼接问题」。无需写XML、无需拼接字符串、无需判空链式调用自动生成安全的 WHERE 条件同时杜绝 SQL 注入风险。1四大构造器介绍项目固定选型Wrapper 分为查询、更新两大体系「开发一律优先 Lambda 版本」编译校验不报错、重构不翻车。「QueryWrapper」查询/删除字段写字符串硬编码不推荐「LambdaQueryWrapper」查询/删除Lambda方法引用企业首选「UpdateWrapper」更新条件字符串字段不推荐「LambdaUpdateWrapper」更新条件Lambda安全写法企业首选// 快速创建方式推荐 LambdaQueryWrapperUser lqw Wrappers.lambdaQuery(); LambdaUpdateWrapperUser luw Wrappers.lambdaUpdate();2常用条件语法全覆盖下面是开发「99% 场景」会用到的条件整理成可直接复制的模板。① 等值条件eq / ne- eq等于 - ne不等于 // name 张三 AND age ! 18 lqw.eq(User::getName, 张三) .ne(User::getAge, 18);② 区间大小条件gt / ge / lt / le / between- gt大于 - ge大于等于 - lt小于 - le小于等于 - between区间范围// age 18 AND age 60 lqw.gt(User::getAge, 18) .le(User::getAge, 60); // age BETWEEN 18 AND 30 lqw.between(User::getAge, 18, 30);③ 模糊查询like 系列- like前后模糊 %值%- likeRight右模糊 值%- likeLeft左模糊 %值// 姓名包含“李” lqw.like(User::getName, 李); // 手机号以 138 开头 lqw.likeRight(User::getPhone, 138);④ 空值判断isNull / isNotNull// 邮箱为 NULL lqw.isNull(User::getEmail); // 手机号不为空 lqw.isNotNull(User::getPhone);⑤ 包含集合in / notIn// 部门ID在 1、2、3 中 lqw.in(User::getDeptId, Arrays.asList(1L,2L,3L));3逻辑关系AND / OR 与括号嵌套重点难点「默认规则」链式连续调用默认是AND关系。「普通OR」直接拼接 or()// name张三 OR age18 lqw.eq(User::getName, 张三) .or() .gt(User::getAge, 18);「带括号复杂逻辑必须用lambda子表达式」场景name 张三 AND (age 18 OR deptId 2)lqw.eq(User::getName, 张三) .and(w - w.gt(User::getAge, 18).or().eq(User::getDeptId, 2));场景name 张三 OR (age 18 AND deptId 2)lqw.eq(User::getName, 张三) .or(w - w.gt(User::getAge, 18).eq(User::getDeptId, 2)); 核心要点「只要需要括号必须使用 and(λ) / or(λ) 嵌套」否则SQL优先级错乱。4condition 动态条件开发最常用、消除大量ifWrapper 所有条件方法「第一个参数为 boolean condition」-「condition true」拼接该SQL条件-「condition false」直接忽略该条件不拼接专门用于前端传参、非必填查询条件「彻底消灭 if 判断嵌套」。// 前端参数 String name ; Integer minAge 18; // 有姓名才模糊查有最小年龄才判断 lqw.like(StringUtils.hasText(name), User::getName, name) .ge(minAge ! null, User::getAge, minAge);等价传统写法但代码精简 80%if (StringUtils.hasText(name)) { lqw.like(User::getName, name); } if (minAge ! null) { lqw.ge(User::getAge, minAge); }5、字段查询过滤指定返回字段Wrapper 支持select()方法自定义查询字段只返回需要的列避免查询全字段、查询大文本字段优化SQL性能、减少返回数据体积是日常优化高频用法。只支持 Lambda 安全写法杜绝字段硬编码。// 只查询 id、姓名、手机号忽略其他所有字段 LambdaQueryWrapperUser lqw Wrappers.lambdaQuery(); lqw.select(User::getId, User::getName, User::getPhone) .eq(User::getDeleted, 0); ListUser userList userMapper.selectList(lqw);适用场景列表页展示、分页查询、接口返回精简数据过滤冗余大字段头像、备注、大文本。6更新构造器与核心注意事项更新构造器LambdaUpdateWrapper用于构建动态更新条件实现安全的局部字段更新是业务开发中替代实体全量更新的主流写法。1、基础使用示例通过链式set指定需要更新的字段搭配where条件精准更新数据仅修改声明的字段其余数据库字段保留原值。// 将部门ID1的所有用户年龄统一更新为25 LambdaUpdateWrapperUser luw Wrappers.lambdaUpdate(); // 仅设置需要更新的字段 luw.set(User::getAge, 25) .eq(User::getDeptId, 1L); // 第一个参数传null表示不使用实体对象赋值全部由Wrapper控制更新逻辑 userMapper.update(null, luw);2、开发核心注意事项两大高频坑点「注意事项1普通set() 存在空值覆盖数据风险」原生无参set()无自动判空逻辑若传入null、空字符串会强制覆盖数据库原有字段值。前端局部编辑、动态传参场景极易造成数据清空。解决方案使用「带条件set(condition)「自定义非空判断或直接使用」setIfPresent」自动忽略null值规避空值覆盖问题。「注意事项2updateById 实体更新存在全量覆盖坑」updateById属于「全量字段覆盖更新」原理是根据实体主键匹配数据实体中所有null字段会直接清空数据库对应原值。仅适合完整实体保存、全字段修改场景「绝对不适合局部编辑」。「核心选型总结」日常局部动态更新优先使用 Wrapper 链式更新增量更新、安全不丢数据避免直接使用 updateById 处理部分字段修改。2.3 分页功能开箱即用物理分页MP提供内置分页拦截器无需手写limit自动适配MySQL、Oracle等多数据库纯物理分页性能稳定。1. 前置配置必须注册分页插件Configuration public class MyBatisPlusConfig { Bean public MybatisPlusInterceptor mpInterceptor(){ MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); // MySQL分页插件 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }2. Mapper层分页使用// 参数1当前页参数2每页条数 PageUser page new Page(1, 10); LambdaQueryWrapperUser wrapper Wrappers.lambdaQuery().eq(User::getDeleted,0); PageUser pageResult userMapper.selectPage(page, wrapper); // 分页结果获取 ListUser dataList pageResult.getRecords(); // 当前页数据 long total pageResult.getTotal(); // 总条数 long pages pageResult.getPages(); // 总页数3. Service层极简分页推荐业务使用// 链式lambda分页 PageUser pageData userService.lambdaQuery() .eq(User::getAge, 18) .page(new Page(1,10));4. 自定义SQL分页多表联查/复杂统计 必备上述分页方式仅适用于 MP 链式单表查询实际开发中「多表联查、子查询、复杂统计、自定义条件SQL」无法使用 Wrapper需要手写 XML 自定义SQL。MP 分页插件完美支持「自定义SQL分页」无需手动写 count 总数、无需拼接 limit只需遵循固定格式插件自动「拦截SQL、自动分页、自动统计总条数」。核心规则「Mapper方法第一个参数必须是 Page 对象返回值必须是 Page 对象」。第一步Mapper 接口定义方法// 自定义多表分页查询 PageUserVO selectUserPage(Param(page) PageUserVO page, Param(name) String name);第二步XML 手写自定义SQL无需写limit、无需写countselect idselectUserPage resultTypecom.xxx.vo.UserVO SELECT u.id, u.name, u.phone, d.dept_name FROM sys_user u LEFT JOIN sys_dept d ON u.dept_id d.id where if testname ! null and name ! AND u.name LIKE CONCAT(%,#{name},%) /if AND u.deleted 0 /where /select第三步业务层调用// 构建分页参数 PageUserVO page new Page(1,10); // 调用自定义分页方法 PageUserVO resultPage userMapper.selectUserPage(page, 张三); // 获取分页数据 ListUserVO records resultPage.getRecords(); long total resultPage.getTotal();关键说明「无需手动分页」MP插件自动拦截SQL拼接 LIMIT 分页语句「无需手动COUNT」插件自动生成 count 查询统计总条数「支持返回VO」自定义分页可以返回自定义视图对象不限于实体类「适配所有复杂SQL」多表联查、分组统计、子查询全部支持5. 分页扩展特性性能容错优化1.「关闭Count统计大数据分页性能优化」对于海量数据列表、无需展示总条数/总页数的导出、滚动加载场景可手动关闭count总数查询避免全表统计耗时大幅提升分页性能。构造Page对象时传入第三个参数 false 即可new Page(1,10,false)。2.「分页溢出容错」开启插件溢出适配当请求页码超过最大页数时自动跳转到最后一页避免返回空数据配置setOverflow(true)。3.「兼容自定义复杂SQL分页」手写XML多表联查、子查询、分组统计等复杂SQL可直接传入Page对象MP分页插件自动拦截SQL、自动处理分页Limit与总数统计无需手动编写分页逻辑。2.4 其他实用语法糖企业开发标配 完整配置1逻辑删除软删除不物理删除数据通过deleted字段标记删除状态适配数据溯源、恢复场景。1. yml全局配置mybatis-plus: global-config: db-config: logic-delete-field: deleted logic-not-delete-value: 0 # 未删除 logic-delete-value: 1 # 已删除2. 实体增加标记字段TableLogic private Integer deleted;生效原理无需业务代码干预调用 Mapper 层 delete 删除方法时底层自动将 DELETE 语句转为 UPDATE 更新语句所有查询、分页、列表查询会自动拼接WHERE deleted 0自动过滤已删除数据无需手动处理。2自动填充 创建/更新时间无需业务代码手动set时间新增、修改时自动赋值create_time、update_time。1. 实体字段注解TableField(fill FieldFill.INSERT) private LocalDateTime createTime; TableField(fill FieldFill.INSERT_UPDATE) private LocalDateTime updateTime;2. 注册全局自动填充处理器核心配置必须配置新建自定义填充类实现MetaObjectHandler交给Spring管理统一自动填充创建时间、更新时间、创建人、更新人等字段。Component publicclass MyMetaHandler implements MetaObjectHandler { Override public void insertFill(MetaObject metaObject) { // 新增时自动填充创建时间、更新时间 this.strictInsertFill(metaObject, createTime, LocalDateTime::now, LocalDateTime.class); this.strictInsertFill(metaObject, updateTime, LocalDateTime::now, LocalDateTime.class); } Override public void updateFill(MetaObject metaObject) { // 修改时只更新更新时间 this.strictUpdateFill(metaObject, updateTime, LocalDateTime::now, LocalDateTime.class); } }生效原理新增/修改数据时MP 自动触发填充逻辑无需业务代码手动 set 时间字段全局统一规范。3乐观锁并发更新防覆盖高并发场景防止多人同时更新覆盖数据依靠version版本号实现无锁并发控制。1. 实体添加版本字段Version private Integer version;2. 注册乐观锁插件核心配置必须配置在 MP 全局配置类中添加乐观锁拦截器否则版本号不会自动递增、并发控制失效。Configuration public class MyBatisPlusConfig { Bean public MybatisPlusInterceptor mpInterceptor(){ MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); // 分页插件 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 乐观锁插件新增 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; } }3. 更新执行逻辑更新数据时自动携带 where 版本条件更新成功后版本号自动1若并发导致版本不匹配更新行数为0更新失败避免数据覆盖。3. 更新逻辑更新时携带原版本号版本匹配才更新版本不匹配更新失败。4主键策略 TableId统一管理主键生成规则适配单机自增、分布式唯一IDTableId(type IdType.AUTO) private Long id;可通过 YAML 全局统一配置无需每个实体单独注解可选mybatis-plus: global-config: db-config: id-type: assign_id # 全局默认雪花算法常用策略说明-AUTO数据库自增MySQL单机-ASSIGN_ID雪花算法分布式Long唯一IDMP默认-ASSIGN_UUID字符串UUID主键。MyBatis-Plus 除基础CRUD与语法糖外内置大量企业级高阶插件与防护能力适配生产环境安全、性能、SaaS多租户、数据脱敏等场景全部开箱即用。2.5 高阶增强功能企业级进阶能力1SQL 日志打印开发调试必备全局开启SQL日志完整打印执行SQL、参数、执行耗时方便排查BUG、优化SQL。yml配置mybatis-plus: configuration: # 开启SQL日志输出 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl效果控制台完整输出最终执行的SQL语句、查询参数、结果行数可直接复制到数据库执行调试。2SQL 性能分析插件慢SQL监控用于开发、测试环境监控慢SQL拦截超时SQL避免劣质SQL上线导致数据库卡顿。可自定义超时阈值超时自动打印警告日志。Bean public MybatisPlusInterceptor mpInterceptor(){ MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); // 慢SQL监控超时200ms告警 PerformanceInnerInterceptor performanceInterceptor new PerformanceInnerInterceptor(); performanceInterceptor.setMaxTime(200); interceptor.addInnerInterceptor(performanceInterceptor); return interceptor; }生产环境建议关闭仅用于开发调优。3防全表更新/删除生产安全核心MP 内置「防误操作安全机制」默认禁止无条件全表更新、全表删除彻底杜绝开发人员误写空条件导致的数据清空事故生产刚需。原理当 Wrapper 无任何查询条件、直接执行 update、delete 时直接抛出异常阻断SQL执行。如需临时关闭超级管理员、后台批量操作场景可手动配置放行禁止业务代码随意关闭。4数据脱敏插件隐私数据防护内置字段自动脱敏能力无需手动代码处理查询返回自动脱敏适配手机号、身份证、邮箱、地址等隐私字段。支持注解配置脱敏规则掩码、中间隐藏、首尾展示等新增/修改数据库存储原始明文「仅查询返回脱敏」不影响数据库真实数据。5多租户插件SaaS系统必备MP 官方内置多租户数据隔离插件适配 SaaS、多商户、多团队共用一套数据库场景实现「代码零侵入全局数据隔离」。核心原理拦截所有CRUD SQL自动追加租户条件无需业务代码手动拼接租户ID。核心细节默认租户字段tenant_id可自定义任意字段名如 merchant_id、company_id租户ID由业务拦截器解析Token后存入 ThreadLocal插件仅读取不生成支持自定义忽略表字典表、公共配置表、租户表本身不隔离支持超级管理员关闭租户隔离查询全量数据核心配置示例interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() { // 获取当前登录租户ID Override public Long getTenantId() { return UserContext.getTenantId(); } // 自定义租户字段名可不写默认tenant_id Override public String getTenantField() { returntenant_id; } // 忽略隔离的公共表 Override public boolean ignoreTable(String tableName) { return Arrays.asList(sys_dict,sys_tenant).contains(tableName); } }));6游标分页大数据量高性能分页区别于普通 offset 分页「游标分页适配百万级大数据列表、数据导出场景」解决传统分页越往后越慢的性能痛点。优势无需 count 统计总数性能极高基于主键游标滚动查询避免深分页偏移性能衰减适合批量数据同步、文件导出、数据清洗场景三、入门快速上手步骤极简流程1. SpringBoot项目引入mybatis-plus-boot-starter依赖移除原生MyBatis启动器2. application.yml配置数据库、开启下划线转驼峰、打印SQL日志3. 实体类使用TableName、TableId、TableField映射数据库字段4. Mapper接口继承BaseMapper启动类添加MapperScan扫描Mapper5. Service层实现IServiceServiceImpl分层6. 需要分页、乐观锁则注册对应插件7. 使用LambdaWrapper拼接动态条件调用内置CRUD方法完成业务。四、总结1. MP核心价值封装单表通用操作把开发人员从重复SQL中解放2. 分层CRUDBaseMapper负责底层DAOIService适配业务层批量、链式操作3. Wrapper条件构造器优先使用Lambda写法配合condition动态条件彻底消灭冗余if支持复杂and/or括号逻辑4. 分页、逻辑删除、自动填充、乐观锁、主键策略均为内置语法糖2.4章节开箱即用高阶能力2.5章节支持SQL监控、数据脱敏、多租户隔离、大数据游标分页、生产防误删防护5. MP不替代MyBatis复杂多表、统计查询依旧支持自定义XML两者可共存。