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接口,为后续流程动态配置提供数据支撑。
4.2 实现流程
- 配置 Flyway 依赖与数据源:在
pom.xml或 Gradle 中添加 Flyway 和 MySQL 驱动。 - 编写迁移脚本:在
src/main/resources/db/migration目录下创建按版本命名的 SQL 文件。 - 定义实体类:使用
@TableName、@TableId、@TableField注解映射表字段。 - 创建 Mapper 接口:继承
BaseMapper,添加@Mapper注解。 - 启用 MyBatis‑Plus 配置:在 Spring Boot 启动类添加
@MapperScan,配置分页插件等。 - 实现配置表 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)的数据库结构完全一致。