4. 核心存储层实现:MyBatis‑Plus 实体与 Mapper、Flyway 数据库版本管理、配置表 CRUD 接口

本章完成审批流动态配置原型的数据持久化基座。我们将使用 Flyway 管理数据库版本演进,MyBatis‑Plus 自动生成实体与 Mapper,并实现配置表的 CRUD 接口。通过这一层的固化,上层业务代码可以与具体数据库解耦,并保持版本可追溯、可回滚。

学习目标:理解 ORM 映射与版本迁移的分工,掌握 Flyway 迁移脚本的编写规范,以及 MyBatis‑Plus 实体的注解与 Mapper 的注入方式。

4.1 原理:ORM + 版本管理 + 通用 CRUD

  • Flyway:通过约定优于配置的方式,将 SQL 脚本按版本号(如 V1__init.sql)存放,首次启动自动执行,并在 flyway_schema_history 表中记录执行历史。支持基线(baseline)与迁移(migrate)两种模式。
  • MyBatis‑Plus:在 MyBatis 基础上提供 Mapper 自动注入、实体映射、分页插件等能力。开发者只需定义实体 Bean 和 Mapper 接口,即可获得基础 CRUD 方法,无需手写 SQL。
  • 配置表 CRUD:针对审批流程中的节点、条件、动作等配置,统一使用一张或几张配置表存储,通过通用 Mapper 提供 insert/update/select/delete 接口,为后续流程动态配置提供数据支撑。
核心存储层组件关系 Flyway 迁移脚本 V1__init.sql 数据库 flyway_schema_history MyBatis‑Plus 实体 @TableName(“wf_config”) Mapper 接口 extends BaseMapper 服务层 CRUD insert / update / select / delete 业务代码调用 @Autowired Mapper 关键说明 ① Flyway 执行迁移脚本后,自动创建表结构并记录版本。 ② 实体通过 @TableName + @TableId 映射到对应表,Mapper 继承 BaseMapper 获得基础 CRUD。 ③ 服务层直接注入 Mapper,无需额外 SQL,即可完成配置项增删改查。

4.2 实现流程

  1. 配置 Flyway 依赖与数据源:在 pom.xml 或 Gradle 中添加 Flyway 和 MySQL 驱动。
  2. 编写迁移脚本:在 src/main/resources/db/migration 目录下创建按版本命名的 SQL 文件。
  3. 定义实体类:使用 @TableName、@TableId、@TableField 注解映射表字段。
  4. 创建 Mapper 接口:继承 BaseMapper,添加 @Mapper 注解。
  5. 启用 MyBatis‑Plus 配置:在 Spring Boot 启动类添加 @MapperScan,配置分页插件等。
  6. 实现配置表 CRUD 服务:编写 Service 类注入 Mapper,暴露增删改查方法。

4.3 关键代码与配置

4.3.1 Flyway 迁移脚本示例

文件位置:src/main/resources/db/migration/V1__create_wf_config.sql

CREATE TABLE `wf_config` (
  `id`          BIGINT      NOT NULL AUTO_INCREMENT COMMENT '主键',
  `config_key`  VARCHAR(64) NOT NULL COMMENT '配置键',
  `config_value` TEXT       COMMENT '配置值(JSON 格式)',
  `description` VARCHAR(255) DEFAULT NULL COMMENT '描述',
  `enabled`     TINYINT(1)  NOT NULL DEFAULT 1 COMMENT '是否启用',
  `create_time` DATETIME    NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` DATETIME    NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_config_key` (`config_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='审批流程配置表';
Flyway 命名规范:脚本文件名必须严格遵循 V{版本号}__{描述}.sql 格式,版本号建议使用数字+下划线(如 V2_1__add_column.sql)。Flyway 优先按版本号排序执行,版本号不能重复。

4.3.2 实体类(MyBatis‑Plus 注解)

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@TableName("wf_config")
public class WfConfig {
    @TableId(type = IdType.AUTO)
    private Long id;

    @TableField("config_key")
    private String configKey;

    @TableField("config_value")
    private String configValue;

    private String description;

    private Boolean enabled;

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

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

4.3.3 Mapper 接口

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface WfConfigMapper extends BaseMapper {
    // 无需额外方法,基础 CRUD 已继承
}

4.3.4 服务层(实现配置表 CRUD)

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class WfConfigService {

    @Autowired
    private WfConfigMapper wfConfigMapper;

    // 插入配置
    public int addConfig(WfConfig config) {
        return wfConfigMapper.insert(config);
    }

    // 根据主键更新
    public int updateConfig(WfConfig config) {
        return wfConfigMapper.updateById(config);
    }

    // 根据主键删除
    public int deleteConfig(Long id) {
        return wfConfigMapper.deleteById(id);
    }

    // 根据主键查询
    public WfConfig getConfigById(Long id) {
        return wfConfigMapper.selectById(id);
    }

    // 根据 configKey 查询(使用 MyBatis‑Plus 的 QueryWrapper)
    public WfConfig getConfigByKey(String configKey) {
        return wfConfigMapper.selectOne(
                new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper()
                        .eq("config_key", configKey));
    }

    // 查询所有启用的配置
    public List listEnabledConfigs() {
        return wfConfigMapper.selectList(
                new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper()
                        .eq("enabled", true));
    }
}
MyBatis‑Plus 常用注解对照表(针对实体字段映射)
注解作用常见值
@TableName指定映射的表名"wf_config"
@TableId标识主键,可指定生成策略IdType.AUTO / IdType.ASSIGN_ID
@TableField指定字段名、填充策略、是否存在exist=false / fill=FieldFill.INSERT
@Version乐观锁字段(可选)放在 Long 型字段上
@TableLogic逻辑删除字段(可选)value = "0", delval = "1"

4.4 调试与验证

  • 启动日志检查:项目启动时,控制台应看到 Flyway 输出的 “Current version of schema `xxx`: << empty >>” 和 “Migrations executed: 1”。
  • 数据库验证:连接数据库,查看是否存在 wf_config 表以及 flyway_schema_history 记录。
  • CRUD 测试:编写简单的单元测试或使用 @PostConstruct 临时插入一条记录,观察 Mapper 方法是否正常返回。
  • 常见错误:
    • Flyway 脚本重复执行:当更改脚本内容但版本号不变时,Flyway 会拒绝执行并抛出校验异常。解决方案:将旧版本基线化或创建新版本脚本。
    • 实体字段与表字段不匹配:检查 @TableField 的 value 是否与数据库字段一致;MyBatis‑Plus 默认使用下划线转驼峰,若不一致需显式指定。
    • Mapper 未注入:确认 @MapperScan 包路径覆盖 Mapper 接口所在包,或每个 Mapper 上标注 @Mapper。

4.5 性能与稳定性优化

  • 合理索引:在 config_key 上建立唯一索引(已在 DDL 中定义),避免后续查询时全表扫描。若需按 enabled 筛选,可考虑复合索引。
  • 分页查询:当配置表数据量超过万级时,服务层应使用 MyBatis‑Plus 的 Page 对象进行分页,而不是一次性加载所有记录。
  • 缓存配置:对于频繁读取且不常变更的配置,可在服务层增加本地缓存(如 Caffeine),减少数据库访问。注意写入时更新缓存。
  • 连接池配置:在 application.yml 中合理设置 HikariCP 的最大连接数、超时时间,避免并发高峰时连接耗尽。
  • Flyway 迁移可重复性:禁止修改已执行过的脚本(除非使用 flyway.repair)。所有数据库变更应通过新增版本脚本实现,保证版本可追溯、可回滚。
生产环境建议:使用 Flyway 的 baselineOnMigrate=true 配合已有数据库的基线版本,同时将 Flyway 的迁移操作集成到 CI/CD 管道中,确保不同环境(dev/staging/prod)的数据库结构完全一致。