一句话总结
MyBatis-Plus 是 MyBatis 的增强工具,只做增强不做改变。核心功能:BaseMapper(通用 CRUD,继承即用)、Wrapper 条件构造器(LambdaQueryWrapper 类型安全)、分页插件(PaginationInnerInterceptor)、代码生成器(AutoGenerator,生成 Entity/Mapper/Service/Controller)、自动填充(@TableField fill)、逻辑删除(@TableLogic)、乐观锁(@Version)。本质是 MyBatis 插件 + 代码生成 + 注解增强的组合。
初级理解
MyBatis-Plus vs MyBatis
| 对比维度 | MyBatis | MyBatis-Plus |
| CRUD 操作 | 需手写 SQL | BaseMapper 内置 CRUD |
| 条件查询 | 手写动态 SQL | Wrapper 条件构造器 |
| 分页 | 需引入 PageHelper | 内置分页插件 |
| 代码生成 | 需第三方工具 | 内置 AutoGenerator |
| 关系 | 基础框架 | 增强工具(依赖 MyBatis) |
BaseMapper 通用 CRUD
# 定义 Mapper 接口,继承 BaseMapper
public interface UserMapper extends BaseMapper<User> {
// 无需写任何方法,自动拥有以下能力
}
# BaseMapper 提供的方法(部分):
// 插入
int insert(T entity);
// 删除
int deleteById(Serializable id);
int deleteByMap(Map<String, Object> columnMap);
int delete(Wrapper<T> wrapper);
// 更新
int updateById(T entity);
int update(T entity, Wrapper<T> updateWrapper);
// 查询
T selectById(Serializable id);
List<T> selectBatchIds(Collection<? extends Serializable> idList);
List<T> selectByMap(Map<String, Object> columnMap);
List<T> selectList(Wrapper<T> queryWrapper);
Page<T> selectPage(Page<T> page, Wrapper<T> queryWrapper);
# 使用示例:
User user = userMapper.selectById(1L);
List<User> users = userMapper.selectList(null); // 查全部
userMapper.insert(new User("张三", 25));
中级深入
Wrapper 条件构造器
# Wrapper 继承体系
Wrapper<T> (抽象类)
├── AbstractWrapper<T, R, Children>
│ ├── QueryWrapper<T> # 字符串方式
│ ├── UpdateWrapper<T> # 更新条件
│ └── AbstractLambdaWrapper<T>
│ ├── LambdaQueryWrapper<T> # Lambda 方式(推荐)
│ └── LambdaUpdateWrapper<T> # Lambda 更新
# QueryWrapper(字符串方式,字段名硬编码)
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name", "张三") // name = '张三'
.gt("age", 18) // age > 18
.like("email", "@qq.com") // email LIKE '%@qq.com%'
.orderByDesc("create_time");
# LambdaQueryWrapper(推荐,类型安全)
LambdaQueryWrapper<User> lambda = new LambdaQueryWrapper<>();
lambda.eq(User::getName, "张三")
.gt(User::getAge, 18)
.like(User::getEmail, "@qq.com")
.orderByDesc(User::getCreateTime);
# 常用条件方法:
eq / ne # 等于 / 不等于
gt / ge / lt / le # 大于 / 大于等于 / 小于 / 小于等于
like / notLike / likeLeft / likeRight # 模糊查询
in / notIn # IN 查询
isNull / isNotNull # 空值判断
between / notBetween # 区间查询
orderByAsc / orderByDesc # 排序
groupBy / having # 分组
or / and # 逻辑拼接(默认 AND)
分页插件
# 1. 配置分页插件
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(
new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
# 2. 使用分页
Page<User> page = new Page<>(1, 10); // 第1页,每页10条
Page<User> result = userMapper.selectPage(page, null);
System.out.println("总记录数: " + result.getTotal());
System.out.println("总页数: " + result.getPages());
System.out.println("当前页数据: " + result.getRecords());
# 3. 分页 + 条件查询
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(User::getAge, 18);
Page<User> result = userMapper.selectPage(
new Page<>(1, 10), wrapper);
# 原理:PaginationInnerInterceptor 拦截 Executor.query()
# 1. 先执行 COUNT 查询
# 2. 再执行分页查询(追加 LIMIT)
高级进阶
自动填充
# 1. 实体类标注 @TableField
@Data
public class User {
@TableId(type = IdType.AUTO)
private Long id;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
# 2. 实现 MetaObjectHandler
@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());
}
}
# FieldFill 枚举:
# INSERT → 插入时填充
# UPDATE → 更新时填充
# INSERT_UPDATE → 插入和更新时都填充
逻辑删除
# 1. 全局配置
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 逻辑删除字段
logic-delete-value: 1 # 已删除值
logic-not-delete-value: 0 # 未删除值
# 2. 实体类标注 @TableLogic
@Data
public class User {
private Long id;
@TableLogic
private Integer deleted; // 0=未删除, 1=已删除
}
# 效果:
# userMapper.deleteById(1L);
# → UPDATE user SET deleted = 1 WHERE id = 1 AND deleted = 0
# userMapper.selectById(1L);
# → SELECT * FROM user WHERE id = 1 AND deleted = 0
# 注意:逻辑删除后,所有查询自动拼接 deleted = 0
# 如需查询已删除数据,需自定义 SQL
乐观锁
# 1. 配置乐观锁插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
# 2. 实体类标注 @Version
@Data
public class User {
private Long id;
private String name;
@Version
private Integer version; // 版本号
}
# 效果:
# 更新时自动拼接 version 条件
# UPDATE user SET name = '李四', version = version + 1
# WHERE id = 1 AND version = 3
# 如果 version 不匹配(被其他线程更新过),更新失败
# 返回 affected rows = 0,需重试
实战场景
# 场景1:复杂条件查询
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.nested(w -> w.eq(User::getStatus, 1)
.or()
.eq(User::getStatus, 2))
.like(StringUtils.hasText(name), User::getName, name)
.gt(age != null, User::getAge, age)
.orderByDesc(User::getCreateTime);
# 场景2:只更新部分字段
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.set(User::getName, "新名字")
.set(User::getAge, 30)
.eq(User::getId, 1L);
userMapper.update(null, wrapper);
# → UPDATE user SET name = '新名字', age = 30 WHERE id = 1
# 场景3:代码生成器
FastAutoGenerator.create(
"jdbc:mysql://localhost:3306/test", "root", "123456")
.globalConfig(builder -> builder
.author("作者").outputDir("D:/code"))
.packageConfig(builder -> builder
.parent("com.example"))
.strategyConfig(builder -> builder
.addInclude("user", "order"))
.execute();
面试模拟
面试官:MyBatis-Plus 和 MyBatis 的关系?解决了什么问题?
你:MyBatis-Plus 是 MyBatis 的增强工具,只做增强不做改变,引入它不会影响原有 MyBatis 功能。主要解决:1)BaseMapper 消除简单 CRUD 的重复 SQL;2)Wrapper 条件构造器替代手写动态 SQL;3)内置分页插件替代 PageHelper;4)代码生成器自动生成 Entity/Mapper/Service/Controller;5)自动填充、逻辑删除、乐观锁等开箱即用功能。
面试官:LambdaQueryWrapper 和 QueryWrapper 有什么区别?
你:QueryWrapper 用字符串指定字段名(如 eq("name", "张三")),字段名硬编码,重构时容易遗漏。LambdaQueryWrapper 用 Lambda 表达式(如 eq(User::getName, "张三")),编译期检查字段名,IDE 可自动重构,类型安全,推荐使用。