🛠️ MyBatis-Plus (MP) 开发全攻略
1. 核心配置与环境
在 pom.xml 中引入依赖后,最关键的配置是日志输出(用于查看生成的 SQL)和分页拦截器。
控制台打印 SQL
YAML
# application.yml
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 标准输出日志
map-underscore-to-camel-case: true # 开启驼峰映射 (默认开启)分页插件配置 (必配)
Java
@Configuration
public class MybatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 指明数据库类型为 MYSQL
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}2. 实体类常用注解
通过注解让 Java 对象与数据库表“对齐”。
@TableName("tb_user"):指定数据库表名。@TableId(type = IdType.AUTO):主键注解。AUTO为数据库自增,ASSIGN_ID为雪花算法生成。@TableField("user_name"):映射非主键字段。@TableField(exist = false):标记该属性在数据库中不存在。@TableLogic:逻辑删除标记(调用delete时实际执行的是update)。
3. 基础 CRUD 方法 (BaseMapper)
只要 Mapper 继承了 BaseMapper<T>,即可直接调用以下方法:
| 操作 | 方法名 | 备注 |
|---|---|---|
| 增 | insert(T entity) | 插入数据,自动回填 ID |
| 删 | deleteById(id) / deleteBatchIds(ids) | 根据 ID 删除 |
| 改 | updateById(T entity) | 最常用。根据 ID 更新非 null 字段 |
| 查 | selectById(id) | 根据 ID 获取单条记录 |
| 查 | selectList(wrapper) | 根据条件查询列表 |
| 数 | selectCount(wrapper) | 查询符合条件的记录总数 |
| 页 | selectPage(page, wrapper) | 核心。结合 Page 对象实现分页 |
4. 条件构造器 (Wrapper) —— 灵魂用法
推荐优先使用 LambdaQueryWrapper,避免硬编码字符串字段名。
常用条件示例:
Java
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getStatus, 1) // 等于 =
.ne(User::getRole, "ADMIN") // 不等于 <>
.gt(User::getAge, 18) // 大于 >
.ge(User::getScore, 60) // 大于等于 >=
.like(User::getName, "王") // 模糊查询 %王%
.between(User::getAge, 20, 30) // 区间 [20, 30]
.isNull(User::getEmail) // 判空
.orderByDesc(User::getCreateTime); // 排序5. 分页查询标准流程
在“黑马点评”商户查询等场景中的标准写法:
Java
// 1. 创建分页参数对象 (当前页, 每页大小)
Page<User> page = new Page<>(1, 10);
// 2. 构建查询条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(User::getName, "Ethan");
// 3. 执行查询
userMapper.selectPage(page, wrapper);
// 4. 获取结果
long total = page.getTotal(); // 总条数
List<User> records = page.getRecords(); // 当前页数据列表
long pages = page.getPages(); // 总页数6. Service 层增强能力
如果你的 Service 继承了 IService<T>,除了基础 CRUD,还多了以下批量操作:
saveBatch(List<T>):批量插入(MP 做了优化,效率比循环插入高得多)。saveOrUpdate(T):有 ID 则更新,无 ID 则插入。list():查询全表。getOne(wrapper):查询单条,若查出多条会报错(可选参数false不报错)。
好的 Ethan,没问题。MyBatis-Plus 的自动填充(AutoFill)是后端开发中非常经典的一个解耦实践。它能让你在插入或更新数据时,不再需要手动 setCreateTime(new Date()),代码会清爽很多。
我为你整理了一份可以直接作为项目笔记的结构:
7. MyBatis-Plus 自动填充全流程指南
一句话:在实体类上规定自动填充的操作类型,配置MyMetaObjectHandler类,规定具体填充逻辑。
实现自动填充主要分为 “标记字段” 和 “编写处理器” 两个核心步骤。
1. 实体类配置:标记填充字段
在你的实体类(Entity)中,使用 @TableField 注解标记哪些字段需要自动填充,并指定填充时机。
Java
@Data
public class User {
private Long id;
private String username;
/**
* 创建时间:仅在插入(insert)时填充
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间:在插入和更新(insert & update)时都填充
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}填充策略 (FieldFill) 说明:
DEFAULT: 默认不处理。INSERT: 插入时填充。UPDATE: 更新时填充。INSERT_UPDATE: 插入和更新时均填充。
2. 编写处理器:定义填充逻辑
你需要创建一个类实现 MetaObjectHandler 接口。这个类必须交给 Spring 管理(加上 @Component),它是填充动作的“大脑”。
Java
@Slf4j
@Component // 必须注入到Spring容器中
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* 插入时的填充策略
*/
@Override
public void insertFill(MetaObject metaObject) {
log.info("开始插入自动填充...");
// 参数:metaObject, 属性名(注意是驼峰名), 属性类型, 填充值
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
/**
* 更新时的填充策略
*/
@Override
public void updateFill(MetaObject metaObject) {
log.info("开始更新自动填充...");
// 更新时只改 updateTime
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}3. 核心注意事项(避坑指南)
A. 填充失效的常见原因
- 手动赋值优先级高:如果你在代码里手动执行了
user.setCreateTime(xxx),MyBatis-Plus 默认不会覆盖你手动设定的值。 - 自定义 SQL 不生效:自动填充只对 MyBatis-Plus 的
BaseMapper方法(如insert,updateById)有效。如果你在 XML 里手写了<insert>标签,它是不会触发这个处理器的。 - 属性名写错:
strictInsertFill的第二个参数必须是 Java 实体类的属性名(如createTime),而不是数据库的列名(如create_time)。
B. 进阶用法:动态获取操作人
在实际项目中(如你提到的“黑马点评”或“苍穹外卖”),我们通常还需要填充 create_user 和 update_user,来记录当前操作者。
你可以结合 ThreadLocal(如项目中的 BaseContext 或 UserHolder)来动态填充:
Java
@Override
public void insertFill(MetaObject metaObject) {
// 从 ThreadLocal 中获取当前登录用户的 ID
Long userId = UserHolder.getUser().getId();
this.strictInsertFill(metaObject, "createUser", Long.class, userId);
}4. 总结对比
| 方式 | 优点 | 缺点 |
|---|---|---|
| 数据库层面 (DEFAULT CURRENT_TIMESTAMP) | 配置最简单,不依赖 Java 代码。 | 不同数据库语法不同,不够灵活,无法记录操作人 ID。 |
| 代码硬编码 (手动 set) | 逻辑直观,无侵入。 | 代码冗余,容易漏写,增加维护成本。 |
| MP 自动填充 | 代码整洁,全局统一管理,支持复杂逻辑(如获取当前操作人)。 | 需要额外维护一个 Handler 类。 |
Ethan,既然你现在在做“黑马点评”,建议你直接把这个 MyMetaObjectHandler 放在 com.hmdp.config 包下。这样当你处理 User 注册或者 Shop 信息更新时,就不再需要手动去管时间戳了。
你现在的 User 实体类里除了时间,还有需要记录“创建人”这类字段吗?
💡 Ethan 的进阶 Tip:
- 查看真实 SQL:安装 IDEA 插件
MyBatis Log Plus,配合log-impl配置,能让你在调试高并发秒杀逻辑时,一眼看穿 SQL 的执行顺序和参数是否正确。 - VO 转换:分页查出的
Page<Entity>可以直接用page.convert(entity -> vo)快速转为前端需要的Page<VO>。