大家好,我是晚安code。
写过几轮 CRUD 的朋友,下面这两件事你大概率天天干:分页查询要手写 count 加 limit,换张表就改一遍;每次 insert 前还得手动 setCreateTime(),漏一次线上就埋个坑。这两件破事,MyBatis-Plus 两个内置能力就能全包圆------分页插件和字段自动填充,两行配置,一次配好。今天用「用户管理」当例子,把思路和代码都给你捋清楚,收藏这篇,下次直接抄。
一、写 CRUD 时最烦的两件事
先说说痛点,你肯定有共鸣。
第一件,分页查询。 早期写分页,每张表都得 copy 一份带 count 和 limit 的 SQL,还得手动算 offset。表一多,光维护这些分页语句就够呛,改个页码逻辑还得小心别把 SQL 拼错。
第二件,审计字段。 也就是 create_time、update_time 这种"谁在什么时候干的"的字段。每次新增、每次修改,都要在 Service 里手动 set 一遍当前时间。手一抖忘了,数据库里就留下一条没有时间戳的记录,排查时一脸懵。


二、MyBatis-Plus 到底是什么
MyBatis-Plus(MP):MyBatis 的增强工具,只做增强不做改变,不会影响你已有的 MyBatis 代码。你可以理解为「MyBatis 的自动挡」------不用换发动机,挂个挡就能自动换挡。
它最拿手的,就是把单表 CRUD 里那些写烦了的模板代码收编成内置能力。分页、条件查询、逻辑删除、字段填充,这些在原生 MyBatis 里要手写的地方,MP 都替你包好了。
三、分页插件:一页一页查,不用再拼 SQL
分页插件解决的不是"能不能分页",而是"别让我每张表都重写一遍 count + limit"。配一次,全项目生效。
分页插件(PaginationInnerInterceptor):MyBatis-Plus 内置的分页拦截器,会自动拦截分页 SQL,帮你拼好 count 统计和 limit 截断。就像点外卖时的"自动凑满减"------你不用自己算,平台帮你凑好。
1)加依赖
xml
<!-- MyBatis-Plus 3.5.x,2026 年 8 月实测 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.7</version>
</dependency>
2)注册插件
java
// MyBatis-Plus 3.5.x,2026 年 8 月实测
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 注册分页插件,指定数据库方言
interceptor.addInnerInterceptor(
new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
DbType.MYSQL 就是告诉插件"我连的是 MySQL",方言不同,分页拼接语法就不同。
3)使用
java
// 查第 2 页,每页 10 条
Page<User> page = new Page<>(2, 10);
// 一条 SQL 都不用写,插件自动拼 count + limit
IPage<User> result = userMapper.selectPage(page, null);
// 当前页的数据
List<User> users = result.getRecords();
// 总条数,前端分页条靠它渲染
long total = result.getTotal();
核心就一个 Page 对象 + 一个 selectPage 方法。至于 count 怎么统计、offset 怎么算、limit 拼在哪儿,都是插件在底层替你干。
看一眼分页插件的执行链路,就明白为什么你不用写 SQL 了
四、踩坑:分页插件失效
可能有人会问:分页插件配了怎么还是不生效?
九成是这俩原因:要么
PaginationInnerInterceptor没注册进MybatisPlusInterceptor(记得加进addInnerInterceptor),要么mybatis-plus版本太旧,老版本分页插件类和现在不一样。先把版本升到 3.5.x 再试。
另外有个细节我踩过:分页插件要声明在拦截器链里 。有些教程让直接 new PaginationInnerInterceptor() 就完事,其实它得挂在 MybatisPlusInterceptor 里面才算数,顺序放前面更稳妥。否则你 selectPage 是能调用,但 SQL 不拼 limit,全表查出来再分页------慢不说,还白折腾。
五、字段自动填充:创建/更新时间不用再手动
审计字段自动填充,本质是把"每次都要记得"改成"配一次永远生效"。你只管业务字段,时间戳交给框架。
MetaObjectHandler(元对象处理器):MyBatis-Plus 的字段填充接口,执行 insert 或 update 时会自动回调。你可以理解为「数据库操作的自动盖章机」------插入或更新时,自动给你盖上当前时间。
审计字段 :数据库表里 create_time、update_time 这类记录"谁、在什么时候、对数据干了什么"的字段。名字听着像审计报告,其实就是让你知道每条数据啥时候来的、啥时候改的。
1)实体加注解
java
@Data
@TableName("user")
public class User {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private String username;
// 插入时自动填充
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
// 插入和更新时都自动填充
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
@TableField(fill = ...) 是开关,FieldFill.INSERT 表示只插入时填,FieldFill.INSERT_UPDATE 表示插入和更新都填。写明白后,Create、Update 两个操作想让它什么时候盖时间戳,你说了算。
2)实现处理器
java
// 注册成 Spring Bean,MyBatis-Plus 自动识别
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime",
LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime",
LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime",
LocalDateTime.class, LocalDateTime.now());
}
}
insertFill 管插入,updateFill 管更新。注意字段名要和实体里一致,"createTime" 对应 createTime,别写成 create_time,不然按字段名填不上。

六、踩坑:自动填充不生效
可能有人会问:自动填充没效果,字段还是空的?
三个原因对号入座:①
@TableField(fill = ...)没写;②MetaObjectHandler忘了加@Component,没注册成 Spring Bean;③strictInsertFill里字段名写错,比如把"createTime"写成"create_time"。
这个坑我实打实踩过。一开始漏了 @Component,insert 完了 create_time 还是 null,查了半天没头绪。

另外说一句,strictInsertFill 只在字段值还是 null 时才填。你要是手动 set 了值,它就尊重你的值,不会覆盖。这个特性挺实用------想特殊处理的字段,你可以自己控制。
七、手动 vs MyBatis-Plus:一张表看清差距
手动写分页和手动填时间戳,是 CRUD 里最不值得花时间的两件事。能交给框架的,别自己扛。
| 场景 | 手动做法 | MyBatis-Plus 做法 | 你要配的 |
|---|---|---|---|
| 分页查询 | 每张表手写 count + limit,换表改一遍 | selectPage + Page,插件自动拼 SQL |
注册一个分页插件 Bean |
| 创建时间 | insert 前手动 setCreateTime() |
@TableField(fill = INSERT) 自动填 |
实现 MetaObjectHandler |
| 更新时间 | update 前手动 setUpdateTime() |
INSERT_UPDATE 插入更新都覆盖 |
同一个 handler 搞定 |
八、总结与延伸
这两样配好之后,我写 CRUD 基本就剩业务逻辑本身了:分页用 Page,时间戳交给 MetaObjectHandler,实体上两个注解,配置类里几行代码。短项目半天能搭完的增删改查,至少省掉一大半模板活。
我的看法很直接:只要你的项目用了 MyBatis-Plus,分页插件和字段自动填充就该是标配,配一次长期省心。 还没用 MP 的项目,光为了这俩能力迁移也值。
想再深入,可以翻 MyBatis-Plus 官方文档看逻辑删除、乐观锁、条件构造器(搜:MyBatis-Plus 官网 核心功能)。工具版本以 3.5.x 为准,本文随版本更新同步修订,最后更新:2026 年 8 月。

我是晚安code,持续分享编程干货。觉得有用的话记得点赞收藏和关注~也欢迎在评论区聊聊:你在用 MyBatis-Plus 时还靠它解决过哪些 CRUD 痛点?