Spring Boot 3 整合 MyBatis-Plus:ORM、Auto DDL 与分页实战

2 阅读4分钟

前言

MyBatis 是 Java 生态中经久不衰的持久层框架,但原生 MyBatis 需要手写大量模板 XML。MyBatis-Plus(简称 MP)在此基础上做了增强:内置通用 CRUD、分页插件、逻辑删除、自动填充、乐观锁、Auto DDL……几乎解决了持久层开发中 80% 的重复劳动

本文结合 personal-blog-backend 项目,讲解:

  1. BOM 依赖引入与 Spring Boot 3 的兼容性
  2. Entity 注解全解(@TableId、@TableField、@TableLogic、@Version)
  3. 分页插件 + 乐观锁 + 防全表删除插件配置
  4. 字段自动填充(审计字段:createTime/updateBy 等)
  5. Auto DDL:用代码替代 Flyway/Liquibase 管理建表脚本

本文所有代码均来自开源项目 personal-blog-backend,基于 Spring Boot 3.5 + Java 21 + MyBatis-Plus 3.5.14 构建,欢迎 Star。


一、依赖引入:使用 BOM 统一版本

MyBatis-Plus 3.5.x 提供了 BOM,在 Spring Boot 3 项目中通过 BOM 引入:

<!-- 根 pom.xml 的 dependencyManagement -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-bom</artifactId>
    <version>3.5.14</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

业务模块只需声明 starter:

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
</dependency>

注意:Spring Boot 3 必须使用 mybatis-plus-spring-boot3-starter(专为 Jakarta EE 适配),而不是旧版的 mybatis-plus-boot-starter,两者并存会 Class 冲突。


二、Entity 注解详解

以项目中的 ArticleEntity 为例:

// blog-module-article/.../domain/entity/ArticleEntity.java

@Data
@TableName("art_article")
public class ArticleEntity {

    @TableId(type = IdType.ASSIGN_ID)   // 雪花算法生成 ID
    private Long id;

    private String title;
    private String content;
    private Integer status;

    // 向量字段,查询时默认跳过
    @TableField(select = false)
    private String embedding;

    @Version                                     // 乐观锁
    private Integer version;

    @TableField(fill = FieldFill.INSERT)         // INSERT 时自动填充
    private Long createBy;

    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    @TableField(fill = FieldFill.INSERT_UPDATE)  // INSERT/UPDATE 都填充
    private Long updateBy;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;

    @TableLogic                                  // 逻辑删除
    private Integer isDeleted;
}

注解速查表

注解作用
@TableName指定数据库表名
@TableId主键字段,IdType.ASSIGN_ID=雪花
@TableField(select=false)查询时忽略,适合 BLOB/VECTOR 大字段
@TableField(fill=...)自动填充触发时机
@Version乐观锁版本字段
@TableLogic逻辑删除,查询自动加 WHERE is_deleted=0

三、插件配置:分页 + 乐观锁 + 防全表删除

// blog-application/.../config/MybatisPlusConfig.java

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

    // 1. 防全表更新/删除(放最前):UPDATE/DELETE 没有 WHERE 则抛异常
    interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());

    // 2. 分页插件
    PaginationInnerInterceptor paginationInterceptor =
            new PaginationInnerInterceptor(DbType.MYSQL);
    paginationInterceptor.setMaxLimit(500L);     // 单页上限,防恶意大量查询
    paginationInterceptor.setOverflow(false);    // 超出总页数返回空
    paginationInterceptor.setOptimizeJoin(true); // 优化 COUNT SQL 中的 JOIN
    interceptor.addInnerInterceptor(paginationInterceptor);

    // 3. 乐观锁(放最后)
    interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());

    return interceptor;
}

分页使用示例:

Page<ArticleEntity> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<ArticleEntity> wrapper = new LambdaQueryWrapper<ArticleEntity>()
        .eq(ArticleEntity::getStatus, 2)
        .orderByDesc(ArticleEntity::getPublishTime);

Page<ArticleEntity> result = articleMapper.selectPage(page, wrapper);
// result.getRecords() 当前页数据
// result.getTotal()   总条数(MP 自动执行 COUNT)
// result.getPages()   总页数

乐观锁使用(必须先查再改):

// ✅ 正确:先查,再更新(version 会被自动附加到 WHERE 条件)
ArticleEntity article = articleMapper.selectById(id); // version=3
article.setTitle("新标题");
int rows = articleMapper.updateById(article); // WHERE id=? AND version=3
if (rows == 0) throw new BusinessException("并发冲突,请重试");

// ❌ 错误:直接 new,version=null,乐观锁失效
ArticleEntity article = new ArticleEntity();
article.setId(1L);
articleMapper.updateById(article); // 不会检查 version

四、字段自动填充(审计字段)

// blog-application/.../handler/MyMetaObjectHandler.java

@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, "createTime", LocalDateTime::now, LocalDateTime.class);
        this.strictInsertFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class);
        Long userId = getCurrentUserId();
        if (userId != null) {
            this.strictInsertFill(metaObject, "createBy", () -> userId, Long.class);
            this.strictInsertFill(metaObject, "updateBy", () -> userId, Long.class);
        }
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class);
        Long userId = getCurrentUserId();
        if (userId != null) {
            this.strictUpdateFill(metaObject, "updateBy", () -> userId, Long.class);
        }
    }

    private Long getCurrentUserId() {
        try {
            return SecurityUtils.getCurrentUserId(); // 从 Spring Security 上下文获取
        } catch (Exception e) {
            log.debug("无法获取当前用户ID(定时任务/初始化场景): {}", e.getMessage());
            return null;
        }
    }
}

注册 Handler:

@Bean
public MetaObjectHandler metaObjectHandler() {
    return new MyMetaObjectHandler();
}

五、Auto DDL

MyBatis-Plus 3.5.3+ 内置 Auto DDL:应用启动时自动执行 SQL 脚本,并在 ddl_history 表记录版本,避免重复执行:

@Component
public class MysqlDdl implements IDdl {
    @Override
    public List<String> getSqlFiles() {
        return Arrays.asList(
            "db/schema/01-system-schema.sql",
            "db/schema/02-article-schema.sql",
            "db/data/01-init-data.sql"
        );
    }
}

六、踩坑记录

1. Spring Boot 3 必须用 spring-boot3-starter:旧 starter 会报 ClassNotFoundException: javax.persistence.Table

2. 乐观锁不生效:必须先 selectById 拿到含 version 的实体,不能直接 new Entity 再 updateById

3. strictInsertFill vs setFieldValByName:使用 strict 系列,仅在字段值为 null 时填充,避免覆盖业务层手动设置的值

4. @TableField(select=false) 的范围:对普通 selectById/selectList 生效,对 selectMaps 或自定义 XML 不生效


总结

特性收益
通用 CRUD无需写 XML,单表操作全覆盖
分页插件自动 COUNT + LIMIT,防大页攻击
乐观锁并发安全,无侵入
防全表误删生产环境兜底保障
自动填充无需手动赋值审计字段
Auto DDL替代 Flyway 轻量管理建表

参考资料

专栏:Spring Boot 3 整合实战:主流技术栈深度整合 源码:github.com/liusxml/per…