MyBatis Plus 高级用法实战:解锁分页、乐观锁与自动填充黑科技
2026/7/10 16:00:10 网站建设 项目流程

引言

MyBatis Plus 作为 MyBatis 的增强工具,凭借其强大的 CRUD 接口和开箱即用的插件生态,已成为 Java 开发中不可或缺的 ORM 框架。在掌握了基础的BaseMapper和条件构造器后,你是否遇到过这些痛点?分页查询需要手动拼装 SQL、并发更新出现数据覆盖、字段自动填充逻辑散落各处…… 本文将以一个完整的 Spring Boot 项目为蓝本,深入讲解 MyBatis Plus 的高级特性,包括分页插件、乐观锁、逻辑删除、自动填充、自定义 SQL 注入等,所有代码均可直接运行,助你从根本上提升开发效率。

1. 核心高级特性概览

在深入编码之前,我们先快速盘点 MyBatis Plus 提供的关键高级能力:

  • 分页插件:一行配置即可实现物理分页,无需在 SQL 中手动拼接LIMIT,支持多数据库方言。
  • 乐观锁:通过@Version注解,在更新时自动拼接版本号校验,防止并发更新覆盖。
  • 逻辑删除:配置实体字段后,删除操作自动转为UPDATE,查询时自动添加deleted=0条件。
  • 自动填充:基于MetaObjectHandler统一处理create_timeupdate_time等字段,告别重复set代码。
  • 自定义 SQL 注入器:扩展SqlInjector或使用AbstractMethod,在BaseMapper中增加全局通用方法。
  • 多租户 & 动态表名:通过插件拦截 SQL,动态替换表名或追加租户过滤条件。
  • 类型处理器:优雅地将 JSON 字段映射为 Java 对象,避免冗余的序列化代码。

本文将重点演示前五项特性的实战组合,覆盖绝大多数业务场景。

2. 项目环境准备

创建一个 Spring Boot 2.7+ 项目,引入以下依赖(pom.xml核心片段):

<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.5</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>

数据库建表语句(包含逻辑删除、版本号、时间字段):

CREATE TABLE `t_user` ( `id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(50) DEFAULT NULL, `age` int DEFAULT NULL, `email` varchar(100) DEFAULT NULL, `version` int DEFAULT '1' COMMENT '乐观锁版本号', `deleted` tinyint DEFAULT '0' COMMENT '逻辑删除(0未删除 1已删除)', `create_time` datetime DEFAULT NULL, `update_time` datetime DEFAULT NULL, PRIMARY KEY (`id`) );

3. 全局配置与插件注册

MyBatis Plus 的诸多高级特性均依赖于插件或全局配置。创建一个配置类,统一注册分页插件、乐观锁插件,并指定逻辑删除与自动填充处理器。

@Configuration @MapperScan("com.example.mpadvanced.mapper") public class MybatisPlusConfig { /** * 分页插件(支持多种数据库,此处以MySQL为例) */ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 分页插件 PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL); // 设置最大单页限制 500 条 paginationInnerInterceptor.setMaxLimit(500L); interceptor.addInnerInterceptor(paginationInnerInterceptor); // 乐观锁插件 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; } /** * 逻辑删除字段与值配置(方式一:配置文件,此处演示注解方式) * 将在实体类中使用 @TableLogic 进行声明,此处不再重复配置 */ /** * 自动填充字段处理器 */ @Bean public MetaObjectHandler metaObjectHandler() { return new MyMetaObjectHandler(); } }

4. 实体类设计(整合所有特性)

使用 Lombok 简化代码,实体类通过注解声明逻辑删除、乐观锁与自动填充字段。

@Data @TableName("t_user") public class User implements Serializable { @TableId(type = IdType.ASSIGN_ID) // 雪花算法 private Long id; private String name; private Integer age; private String email; @Version // 乐观锁版本号 private Integer version; @TableLogic(value = "0", delval = "1") // 逻辑删除 private Integer deleted; @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; }

5. 自动填充处理器实现

统一填充create_timeupdate_time,避免在业务代码中手动赋值。

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()); } }

6. Mapper 接口与自定义全局方法

定义UserMapper,并借此演示自定义 SQL 注入:我们可以在基类中增加一个alwaysUpdateSomeColumnById方法,类似updateById但可指定仅更新非空字段,或添加其他通用逻辑。这里展示扩展BaseMapper注入deleteAll方法(谨慎使用)。

public interface UserMapper extends BaseMapper<User> { // 普通自定义SQL(混合MP条件构造器) @Select("SELECT * FROM t_user WHERE age > #{age} AND deleted = 0") List<User> selectByMinAge(@Param("age") int age); // 与条件构造器结合的高级查询可以在Service层完成 }

自定义通用方法(SQL注入器示例):
创建DeleteAll方法实现,并将其注入到BaseMapper中。

// 自定义方法,注意继承 AbstractMethod public class DeleteAll extends AbstractMethod { @Override public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { String sql = "DELETE FROM " + tableInfo.getTableName(); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); return this.addDeleteMappedStatement(mapperClass, "deleteAll", sqlSource); } }

注册该注入器:

@Component public class MySqlInjector extends DefaultSqlInjector { @Override public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) { List<AbstractMethod> methodList = super.getMethodList(mapperClass, tableInfo); methodList.add(new DeleteAll()); return methodList; } }

然后在配置类中替换默认注入器(可选,此处略),或直接继承BaseMapper扩展,集成起来稍有侵入性。为保持示例简洁,本文重点放在更高频的特性上。

7. Service 层实战:组合高级特性

编写UserService,演示分页查询、乐观锁更新、逻辑删除与自动填充的联动效果。

@Service public class UserService extends ServiceImpl<UserMapper, User> { // 分页查询(使用MP的分页对象) public IPage<User> selectPageByAge(int age, int current, int size) { Page<User> page = new Page<>(current, size); LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.gt(User::getAge, age).orderByDesc(User::getCreateTime); return this.page(page, wrapper); } // 利用乐观锁更新用户邮箱 public boolean updateEmailWithOptimisticLock(Long userId, String newEmail) { // 先查询,获得版本号 User user = this.getById(userId); if (user == null) { return false; } user.setEmail(newEmail); // 执行更新,自动在WHERE中拼接 version = ? return this.updateById(user); } // 逻辑删除 public boolean softDeleteById(Long id) { // 实际执行 UPDATE t_user SET deleted = 1 WHERE id = ? AND deleted = 0 return this.removeById(id); } }

8. 测试验证

编写单元测试或直接启动项目调用接口,观察控制台打印的 SQL 日志。

@SpringBootTest class UserServiceTest { @Autowired private UserService userService; @Test void testInsert() { User user = new User(); user.setName("张三"); user.setAge(25); user.setEmail("zhangsan@example.com"); boolean saved = userService.save(user); Assertions.assertTrue(saved); // 观察控制台:create_time和update_time自动填充,version默认为1,deleted为0 } @Test void testOptimisticLock() { // 模拟并发:两个查询拿到的版本号都是1,第一个更新成功,第二个失败 User u1 = userService.getById(1L); User u2 = userService.getById(1L); u1.setEmail("new1@x.com"); u2.setEmail("new2@x.com"); boolean r1 = userService.updateById(u1); // 成功,版本号变2 boolean r2 = userService.updateById(u2); // 失败,因为版本号条件不匹配 Assertions.assertTrue(r1); Assertions.assertFalse(r2); } @Test void testLogicDelete() { boolean removed = userService.softDeleteById(1L); Assertions.assertTrue(removed); // 执行查询:User u = userService.getById(1L); 将返回null,因为自动拼接deleted=0 User user = userService.getById(1L); Assertions.assertNull(user); } @Test void testPage() { IPage<User> page = userService.selectPageByAge(20, 1, 5); System.out.println("总条数:" + page.getTotal()); page.getRecords().forEach(System.out::println); } }

在所有测试中,我们可以看到:
- 插入时create_timeupdate_time自动填充当前时间。
- 乐观锁更新生成的 SQL 为UPDATE t_user SET email=?, version=?, update_time=? WHERE id=? AND version=? AND deleted=0,若版本号不匹配则更新 0 行。
- 逻辑删除 SQL 为UPDATE t_user SET deleted=1 WHERE id=? AND deleted=0;后续查询自动加上deleted=0条件。
- 分页查询执行SELECT count(*) FROM t_user WHERE age > ? AND deleted=0作为 total 计数,再执行分页 SQL 包含LIMIT ?

9. 常见问题与注意事项

9.1 乐观锁需要version字段

必须为整数类型,且数据库有默认值(如 1)。updateById方法会自动使用当前对象中的version值进行“比较并更新”,因此务必先查询再更新,否则会丢失版本号。Spring 事务嵌套中需注意对象版本一致性。

9.2 逻辑删除全局配置

除了使用@TableLogic注解,也可以在application.yml中统一配置:

mybatis-plus: global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0

注意:全局配置下,实体类无需@TableLogic注解,字段名匹配即可。建议项目采用一种方式即可。

9.3 分页插件必须注册

分页插件不会自动生效,务必在配置类中注册PaginationInnerInterceptor,否则分页参数会被忽略(返回全量数据)。

9.4 自动填充与数据库默认值冲突

当数据库也设置了DEFAULT CURRENT_TIMESTAMP时,二者可能重复。建议选择一种:要么仅依赖应用层自动填充,数据库不设默认值;要么关闭应用层填充,依赖数据库DEFAULT。混合使用可能导致时间不一致。

9.5 自定义 SQL 注入器的风险

注入deleteAll等破坏性通用方法需谨慎评估。推荐使用InsertBatchSomeColumn等有明确业务语义的方法,并仅对特定 Mapper 开放。

10. 总结

MyBatis Plus 的高级特性能够极大简化日常开发中的样板代码,使开发者更加专注于业务逻辑。本文通过一个完整的用户增删改查示例,展示了分页、乐观锁、逻辑删除、自动填充与全局方法注入的整合用法。在实际项目中,这些特性可以灵活组合,再配合多租户、动态表名等插件,能够应对绝大多数企业级场景。

常见的坑点主要集中在插件未注册、乐观锁未先查询、逻辑删除全局与局部配置冲突等方面,只要掌握其底层原理并遵循最佳实践,即可避开。希望本文能成为你进阶 MyBatis Plus 的加速器,让你的代码更加干净、健壮。如果你在实践中有更多高级需求,不妨翻阅官方文档,那里还有更丰富的拦截器与扩展点等你发掘。


参考资源:
- MyBatis Plus 官方文档
- Spring Boot 集成 MyBatis Plus 最佳实践

完整项目源码可关注本文所属专栏,后续将提供 GitHub 链接。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询