Skip to content

MyBatis-Plus 增强 ​

Java 中级 · 持久层框架

MyBatis-Plus(简称 MP)是 MyBatis 的增强工具:只做增强不做改变——引入后原有的 MyBatis 用法完全不受影响,同时白送你通用 CRUD、条件构造器、分页、代码生成、逻辑删除。理解它的实现原理(尤其是「SQL 是怎么被拼出来的」),才能用得放心。

一、通用 CRUD:BaseMapper ​

继承 BaseMapper<T> 即获得 17 个通用方法,无需写 XML。

java
@Mapper
public interface UserMapper extends BaseMapper<User> { }

// 使用
User u = userMapper.selectById(1L);
List<User> list = userMapper.selectList(new QueryWrapper<User>().eq("status", 1));
userMapper.insert(new User("Tom", 18));
userMapper.deleteById(1L);
方法说明
insert(entity)插入(自动回填自增 ID)
deleteById(id)按主键删
updateById(entity)按主键更新非 null 字段
selectById(id)按主键查
selectList(wrapper)条件列表
selectCount(wrapper)计数
selectPage(page, wrapper)分页(需分页插件)

主键策略

@TableId(type = IdType.AUTO) 数据库自增;IdType.ASSIGN_ID 用雪花算法生成 Long;IdType.ASSIGN_UUID 生成字符串。分布式下推荐 ASSIGN_ID。

二、条件构造器:SQL 是怎么被拼出来的 ​

QueryWrapper / LambdaQueryWrapper 是 MP 的灵魂。它把链式调用收集成一个条件树,再在 SQL 注入时拼成 SELECT ... WHERE ...。

java
// 普通 Wrapper:列名硬编码字符串,易错
QueryWrapper<User> qw = new QueryWrapper<>();
qw.eq("status", 1).gt("age", 18).orderByDesc("id");

// Lambda Wrapper:方法引用取列名,编译期安全(推荐)
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
lqw.eq(User::getStatus, 1)
   .gt(User::getAge, 18)
   .like(StringUtils.hasText(name), User::getName, name) // 条件成立才拼
   .orderByDesc(User::getId);
List<User> list = userMapper.selectList(lqw);

生成的 SQL:

sql
SELECT id, name, age, status FROM user
WHERE status = ? AND age > ? AND name LIKE ?
ORDER BY id DESC

2.1 常用条件 ​

方法SQL
eq / ne= / <>
gt / ge / lt / le> >= < <=
like / likeLeft / likeRightLIKE
in / notInIN / NOT IN
isNull / isNotNullIS NULL
betweenBETWEEN
and / or逻辑连接
orderByAsc / orderByDesc排序

动态条件:第一个参数是 boolean

.like(condition, column, value) 的第一个参数为 false 时该条件不拼接。这是 MP 里做动态 SQL 的关键,避免写一堆 <if>。

三、分页插件 ​

MP 分页依赖插件,必须注册 MybatisPlusInterceptor,否则 selectPage 不分页。

java
@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 分页插件(指定数据库类型)
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        // 乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        // 防全表更新删除
        interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
        return interceptor;
    }
}
java
Page<User> page = new Page<>(1, 10);          // 第 1 页,每页 10 条
userMapper.selectPage(page, new LambdaQueryWrapper<User>().eq(User::getStatus, 1));
System.out.println(page.getTotal());          // 总条数
System.out.println(page.getRecords());        // 当前页数据

3.1 分页原理 ​

MP 用 MyBatis 的插件(拦截器)机制拦截 SQL,在 Executor 层做两件事:

深分页优化

LIMIT 1000000, 10 要扫描并丢弃前 100 万行,很慢。改为游标/延迟关联:WHERE id > ? ORDER BY id LIMIT 10,或先查主键再回表。

四、逻辑删除 ​

不真删,只把 deleted 字段置为 1,查询自动加 WHERE deleted = 0。

java
// 实体字段上标注
@TableLogic
private Integer deleted;    // 0 未删,1 已删

// 配置(可选,默认 0/1)
mybatis-plus.global-config.db-config.logic-delete-field: deleted
mybatis-plus.global-config.db-config.logic-delete-value: 1
mybatis-plus.global-config.db-config.logic-not-delete-value: 0

执行 deleteById(1) 实际发的是:

sql
UPDATE user SET deleted = 1 WHERE id = 1 AND deleted = 0

逻辑删除的三个坑

  1. 唯一索引冲突:删除后 deleted=1,再插入同名(如 username)会撞唯一索引。方案:唯一索引带上 deleted 字段,或删除时把 deleted 设为 id。
  2. 查询必须带条件:手写 SQL 时要记得 AND deleted = 0。
  3. 数据量只增不减:需定期归档。

五、代码生成器 ​

MyBatis-Plus-Generator 可根据数据库表一键生成 Entity / Mapper / Service / Controller,减少样板代码。注意生成后要人工审查,别直接上生产。

六、字段自动填充 ​

创建时间/更新时间/操作人等公共字段,用 MetaObjectHandler 自动填充,避免每次手动 set。

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

📚 官方文档与参考资料 ​

✏️ 练习与自测 ​

  1. QueryWrapper 和 LambdaQueryWrapper 有什么区别?为什么推荐后者?
  2. MP 分页的底层原理是什么?为什么一定要注册 PaginationInnerInterceptor?
  3. 逻辑删除后为什么可能撞唯一索引?给出两种解决方案。
  4. 写一段代码:用 LambdaQueryWrapper 实现「按状态筛选 + 按年龄倒序 + 分页」的查询。

本节小结

MP 是「增强不改动」:BaseMapper 提供通用 CRUD,LambdaQueryWrapper 用方法引用安全地拼条件,分页必须注册拦截器(改写 SQL 为 COUNT + LIMIT),@TableLogic 逻辑删除、MetaObjectHandler 自动填充。用爽的同时别忘了:深分页要游标优化、逻辑删除注意唯一索引。