Skip to content

🛠️ 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_userupdate_user,来记录当前操作者。

你可以结合 ThreadLocal(如项目中的 BaseContextUserHolder)来动态填充:

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>