
MyBatis Plus框架在简化数据库操作中的高级特性应用教程:从“能用”到“优雅高效”的实战进阶
作为一名常年与数据库打交道的开发者,我经历过从原生JDBC到各种ORM框架的演变。MyBatis因其灵活性和对SQL的掌控力,一直是我的主力选择。但直到深度使用了MyBatis Plus(简称MP),我才真正体会到什么叫“解放生产力”。它不仅仅是MyBatis的增强工具,更是一套能极大简化CRUD、提升开发体验的“瑞士军刀”。今天,我就结合自己的实战经验,分享几个MP的高级特性,带你超越基础的增删改查,写出更优雅、更高效的代码。过程中也会穿插一些我踩过的“坑”和最佳实践。
一、告别手写SQL:条件构造器与链式调用的艺术
最让我着迷的MP特性,莫过于其强大的条件构造器 `QueryWrapper` 和 `LambdaQueryWrapper`。早期我还在手写 `WHERE column = value` 这样的XML SQL时,MP的构造器让我彻底“戒掉”了简单查询的XML。
实战示例: 假设我们有一个 `User` 实体,需要查询年龄大于25岁、状态为启用、并且姓名包含“张”的用户列表,并按创建时间倒序。
// 传统方式:需要去XML里写一个标签,定义SQL和参数映射
// MyBatis Plus方式:
// 1. 使用QueryWrapper(字段名用字符串)
QueryWrapper wrapper = new QueryWrapper();
wrapper.gt("age", 25)
.eq("status", 1)
.like("name", "张")
.orderByDesc("create_time");
List userList = userMapper.selectList(wrapper);
// 2. 【高级推荐】使用LambdaQueryWrapper(类型安全,防误写)
LambdaQueryWrapper lambdaWrapper = new LambdaQueryWrapper();
lambdaWrapper.gt(User::getAge, 25)
.eq(User::getStatus, 1)
.like(User::getName, "张")
.orderByDesc(User::getCreateTime);
List userList2 = userMapper.selectList(lambdaWrapper);
踩坑提示: 强烈推荐使用 `LambdaQueryWrapper`。我曾经因为手写字符串字段名 `“creat_time”`(少了个e)导致查询失败,这种错误在编译期无法发现,而Lambda方式在编码时就有IDE的智能提示和编译检查,安全得多。
二、自动填充:让“创建时间”等字段自己工作
几乎每个表都有 `create_time`、`update_time` 或者 `create_by` 这样的字段。以前我总是在业务代码里手动 `set`,既繁琐又容易遗漏。MP的元对象处理器(MetaObjectHandler)完美解决了这个问题。
操作步骤:
1. 在实体类字段上添加 `@TableField` 注解,指定填充策略。
@Data
public class User {
// ... 其他字段
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableField(fill = FieldFill.INSERT)
private String createUser;
}
2. 实现 `MetaObjectHandler` 接口,定义填充规则。
@Component // 别忘了加入Spring容器
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());
// 可以从线程上下文或Security中获取当前用户
this.strictInsertFill(metaObject, "createUser", String.class, "system_admin");
}
@Override
public void updateFill(MetaObject metaObject) {
// 更新时填充
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
实战感言: 配置好后,在执行 `insert()` 或 `updateById()` 时,这些字段会自动填充,业务代码变得非常干净。注意 `strictInsertFill` 方法可以避免属性名写错和类型不匹配的问题。
三、逻辑删除:数据无价,安全第一
物理删除数据是危险的。MP内置了逻辑删除功能,让删除操作变为更新状态字段。
配置与使用:
1. 在 `application.yml` 中全局配置(也可在实体字段上用 `@TableLogic` 注解单独配置)。
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 全局逻辑删除的实体字段名
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
2. 实体类中对应字段(推荐使用Integer或Boolean)。
// 数据库表中需存在 `deleted` 字段
private Integer deleted;
3. 使用方式完全不变。
userMapper.deleteById(1L); // 实际执行的是:UPDATE user SET deleted=1 WHERE id=1 AND deleted=0
List list = userMapper.selectList(null); // 自动附加条件 WHERE deleted=0
重要提醒: 一旦启用,MP的所有查询和更新操作都会自动带上 `deleted=0` 条件。如果你真的需要查询已删除的数据,就需要自己构造Wrapper,或者使用 `selectMaps`、`selectObjs` 等不封装实体对象的方法绕过自动过滤。这是我初期没注意,排查了半天“数据不见了”问题的根源。
四、性能与便利的平衡:分页插件与乐观锁
分页插件: MP的分页插件配置简单,且与条件构造器结合得天衣无缝。
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
// 使用示例
Page page = new Page(1, 10); // 当前页,每页大小
LambdaQueryWrapper wrapper = new LambdaQueryWrapper().gt(User::getAge, 20);
Page resultPage = userMapper.selectPage(page, wrapper);
List records = resultPage.getRecords(); // 当前页数据
long total = resultPage.getTotal(); // 总记录数
乐观锁: 在高并发更新场景下非常有用。通过一个版本号字段实现。
1. 实体类添加 `@Version` 注解字段。
@Version
private Integer version;
2. 更新时,MP会自动带上版本条件:`UPDATE ... SET ... WHERE id=? AND version=?`。如果版本不匹配,更新行数为0,你可以在业务层判断以进行重试或提示。
User user = userMapper.selectById(1L);
user.setName("新名字");
int rows = userMapper.updateById(user); // SQL中会自动包含 `WHERE id=1 AND version=旧值`
if (rows == 0) {
throw new RuntimeException("数据已被他人修改,请刷新后重试");
}
实战心得: 分页插件默认会对所有 `selectPage` 调用执行一次 `COUNT(1)` 查询。如果某些场景下你已知数据量巨大且不需要总条数,可以使用 `Page` 的构造函数 `new Page(1, 10, false)` 传入 `false` 来禁用优化计数,提升速度。
五、代码生成器:真正的“一分钟”起步
这是MP的“大杀器”,虽然官方现在更推荐使用独立的 `mybatis-plus-generator`,但它依然是MP生态的一部分。通过它可以一键生成 Entity、Mapper、Service、Controller 层代码,极大地提升了项目初始搭建和对接旧表的效率。
// 一个简单的生成器示例
FastAutoGenerator.create("jdbc:mysql://localhost:3306/test", "root", "password")
.globalConfig(builder -> builder.author("YourName").outputDir("/src/main/java"))
.packageConfig(builder -> builder.parent("com.example.demo"))
.strategyConfig(builder -> builder.addInclude("user", "order") // 要生成的表
.entityBuilder().enableLombok() // 使用Lombok
.controllerBuilder().enableRestStyle()) // 生成@RestController
.execute();
运行这段代码,一个完整的、包含基础CRUD接口的Restful风格后端模块就生成了。你可以根据团队规范,定制模板来生成更符合需求的代码。
总结一下,MyBatis Plus的这些高级特性,核心思想是 “约定优于配置” 和 “避免重复劳动”。它没有剥夺MyBatis编写复杂SQL的能力(你依然可以写XML做复杂查询和关联),而是把开发者从大量简单、模板化的代码中拯救出来。合理运用条件构造器、自动填充、逻辑删除等特性,能让你的代码更健壮、更简洁,从而更专注于核心业务逻辑的实现。希望这篇教程能帮助你在使用MP的路上走得更远、更稳。

评论(0)