第四章:[高级应用] 核心特性与实用工具
第四章:[高级应用] 核心特性与实用工具
Prorise第四章:[高级应用] 插件体系与工程化实战
摘要: 在掌握了 Mybatis-Plus (MP) 的基础 CRUD 和动态查询后,本章将进入 企业级工程化 的深水区。我们将深入探索 MP 的 插件体系,通过配置 分页插件 解决海量数据查询问题,利用 乐观锁插件 解决并发更新难题。同时,我们将引入 ActiveRecord 模式 和 SimpleQuery 工具类,体验如何进一步压缩代码行数,提升开发效率。
本章学习路径
- 模式革新:体验 ActiveRecord (AR) 模式,让实体类“拥有生命”,直接操作数据库。
- 物理分页:配置
PaginationInnerInterceptor,彻底告别手动写LIMIT和COUNT的历史。 - 并发安全:启用 乐观锁插件,通过
version机制优雅解决“丢失更新”问题。 - 自动填充:通过
MetaObjectHandler实现创建时间、更新时间的自动化维护。 - 数据清洗:掌握 SimpleQuery,用一行代码替代繁琐的 Java Stream 操作。
4.1. ActiveRecord (AR) 模式
在标准的三层架构(Controller -> Service -> Dao)中,即便是最简单的“根据 ID 查询用户”,也需要跨越三层调用。这在大型项目中是必要的解耦,但在微服务或简单脚本中,显得有些繁琐。
ActiveRecord (AR) 是一种领域驱动设计(DDD)风格的编程范式。它的核心思想是:实体类(Model)不仅承载数据,还具备操作数据的方法。
4.1.1. 开启 AR 模式
要使用 AR,只需让实体类继承 Model<T>。
文件路径: src/main/java/com/example/mpstudy/domain/UserDO.java
1 | package com.example.mpstudy.domain; |
4.1.2. AR 实战演练
现在,我们的 UserDO 对象已经“活”了,它不再需要依赖 UserMapper 就能自我管理。
文件路径: src/test/java/com/example/mpstudy/ActiveRecordTest.java
1 |
|
选型建议:AR 模式非常适合 原型开发、脚本工具、简单的配置表管理。但在复杂的业务系统中,为了保持分层架构的清晰和可维护性,我们依然推荐使用标准的 Service -> Mapper 模式。
4.2. 物理分页插件 (PaginationInnerInterceptor)
在 Web 开发中,分页是必不可少的。在原生 MyBatis 中,我们通常需要手动写两条 SQL:一条查数据(带 LIMIT),一条查总数(COUNT)。这不仅繁琐,而且容易导致条件不一致。
MP 提供了 分页拦截器,能在 SQL 执行前自动拦截,动态添加 LIMIT 语句,并自动发起 COUNT 查询。
4.2.1. 开启分页插件
在 Spring Boot 中,我们需要通过配置类注册 MP 的拦截器核心组件。
文件路径: src/main/java/com/example/mpstudy/config/MybatisPlusConfig.java
1 | package com.example.mpstudy.config; |
4.2.2. 分页查询实战
配置完成后,使用 Page<T> 对象作为参数即可触发分页。
文件路径: src/test/java/com/example/mpstudy/PageTest.java
1 |
|
1
2
3
4
5
6
7
-- MP 自动执行的第一条 SQL:查询总数
==> Preparing: SELECT COUNT(*) AS total FROM tb_user
<== Total: 1
-- MP 自动执行的第二条 SQL:查询数据(自动拼接了 LIMIT)
==> Preparing: SELECT id,name,age... FROM tb_user LIMIT ?
==> Parameters: 2(Long)
4.3. 乐观锁插件 (OptimisticLocker)
痛点背景:在并发场景下,A 和 B 两个用户同时读取了 ID = 1 的用户数据(Age = 20)。A 将 Age 修改为 21,B 将 Age 修改为 30。A 先提交,B 后提交。最终数据库变成了 30,A 的修改被 B 覆盖了,这就是著名的 丢失更新 问题。
解决方案:乐观锁机制。在数据库增加一个 version 字段。每次更新时,检查版本号是否一致,如果一致则更新并版本号+1,否则更新失败。
4.3.1. 配置乐观锁
步骤 1:注册插件
在 MybatisPlusConfig 中追加乐观锁拦截器。
1 | // MybatisPlusConfig.java |
步骤 2:实体类标识
在实体类的版本字段上添加 @Version 注解。
1 | // UserDO.java |
4.3.2. 并发更新实战
为了验证效果,我们需要模拟“读取-修改-写入”的过程。
1 |
|
4.4. 自动填充 (MetaObjectHandler)
痛点背景:在企业级开发中,gmt_create(创建时间)和 gmt_modified(修改时间)是每张表的标配。如果每次 insert 或 update 都要手动写 user.setGmtCreate(LocalDateTime.now()),不仅代码冗余,还容易遗漏。
解决方案:
MP 提供了 MetaObjectHandler 接口,可以在 SQL 执行前自动填充指定字段。
4.4.1. 实现填充处理器
创建一个 Handler 类,实现 MetaObjectHandler 接口。
文件路径: src/main/java/com/example/mpstudy/handler/MyMetaObjectHandler.java
1 | package com.example.mpstudy.handler; |
4.4.2. 实体类绑定
在 UserDO 中,使用 @TableField 告诉 MP 哪些字段需要填充,以及何时填充。
1 | // UserDO.java |
此后,执行 insert 或 update 时,无需手动设置时间,MP 会自动处理。
4.5. SimpleQuery 工具类
痛点背景:我们经常需要把查询结果 (List<UserDO>) 转换成 Map<Id, User>,或者提取出 List<Id>。虽然 Java 8 Stream 可以做到(如 .stream().map().collect()),但代码仍然偏长。
解决方案:SimpleQuery 是 MP 对 Stream 操作的封装,用一行代码实现常见的集合转换。
4.5.1. 常用 API 实战
文件路径: src/test/java/com/example/mpstudy/SimpleQueryTest.java
1 |
|
4.6. 本章总结与特性速查
4.6.1. 场景化速查
场景一:前端列表分页
1 | // Controller 层 |
场景二:防止数据被覆盖 (乐观锁)
- DB 加
version列 (default 1)。 - 实体类加
@Version。 - 配置
OptimisticLockerInnerInterceptor。 - 更新前 必须先查询(拿到当前 version),再更新。
场景三:自动记录操作时间
- 实现
MetaObjectHandler。 - 实体字段加
@TableField(fill = ...)。 - 告别
setCreateTime。
4.6.2. 核心避坑指南
分页失效
- 现象:查询出来了所有数据,LIMIT 没生效。
- 原因:忘记配置
MybatisPlusInterceptorBean,或者忘记把PaginationInnerInterceptoradd进去。
乐观锁无效
- 现象:并发更新时数据依然被覆盖。
- 原因:
update(entity, wrapper)方法 不支持 乐观锁。必须使用updateById(entity),且 entity 中必须包含从数据库查出来的version值。
ActiveRecord 的滥用
- 建议:AR 模式虽然写起来爽,但它让实体类耦合了 Dao 层逻辑。在大型项目中,建议严格遵守 Service -> Mapper 的调用链,保持实体类的纯洁性(POJO)。







