目录
7、数据库记录表(flyway_schema_history)
🐦 1、什么是 Flyway?
Flyway 是一个开源的数据库版本控制工具,主要用于:
-
管理数据库结构的版本迁移(增删表、字段、索引等)
-
保证数据库 schema 一致性
-
和 Git 管理代码一样管理数据库迁移脚本
它支持多种数据库(如 MySQL、PostgreSQL、Oracle、SQL Server 等),并能与 Spring Boot、Maven、Gradle 等无缝集成。
2、引入依赖
plugins {
id 'org.flywaydb.flyway' version '7.15.0' // 与 flyway-core 7.15.0 兼容
}
implementation 'org.flywaydb:flyway-core:7.15.0'
3、在yml中配置
简单自动执行规定目录下的脚本
spring.flyway.enabled=true
spring.flyway.locations=classpath:scripts/db/flyway/mysql
spring.flyway.clean-disabled=true
spring.flyway.baseline-on-migrate=true
spring.flyway.out-of-order=true
spring.flyway.schemas=etrade
spring.flyway.default-schema=etrade
3.1、在代码中自定义配置
可能因为不同的客户需要执行不同的sql 脚本文件
3.1.1、读取配置
@Configuration
@Data
public class FlywayCustomConfig {
@Value("${spring.flyway.create-schemas:true}")
private Boolean createSchemas;
@Value("${spring.flyway.clean-disabled:true}")
private Boolean cleanDisabled;
@Value("${spring.flyway.baseline-on-migrate:true}")
private Boolean baselineOnMigrate;
@Value("${spring.flyway.out-of-order:true}")
private Boolean outOfOrder;
@Value("${spring.flyway.default-schema:etrade}")
private String defaultSchema;
@Value("${spring.flyway.locations:classpath:scripts/db/flyway/mysql}")
private String[] locations;
}
3.1.2 、自定义读取的flyway 的 读取路径地址
/**
* 初始化Flyway ---> 执行相关SQL脚本
*/
@Slf4j
@Configuration
@RequiredArgsConstructor
// flyway开关,可在application.yml中配置
//@ConditionalOnProperty(prefix = "spring.flyway", name = "enabled", havingValue = "true")
public class FlywayMigrationPostProcessor implements BeanPostProcessor, Ordered {
private static final String VERSION = "version";
private static final String FLYWAY_PATH_PREFIX = "classpath:";
private final DataSource dataSource;
private final FlywayCustomConfig flywayCustomConfiguration;
private final RedissonClient redissonClient;
private final SystemVariableRepo systemVariableRepo;
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 1;
}
@PostConstruct
public void init() {
long start = System.currentTimeMillis();
log.info("Flyway 执行的脚本目录: {}", String.join(",", flywayCustomConfiguration.getLocations()));
String redisKey = "FLYWAY_LOCK";
RLock lock = redissonClient.getLock(redisKey);
try {
boolean locked = lock.tryLock(60, 100, TimeUnit.SECONDS);
log.info("Flyway 配置: {}", flywayCustomConfiguration);
if (!locked) {
throw new CustomException("请稍后再试");
}
// 执行 Flyway 初始化脚本
executeFlyway(start);
} catch (Exception e) {
log.error("Flyway 初始化失败,原因:{}", e.getMessage(),e);
throw new CustomException("Flyway 初始化过程中发生错误" + e);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
private void executeFlyway(Long start) {
log.info("开始执行 Flyway 迁移...");
try {
SqlUtils.setDbType(SqlUtils.getDbType().name());
String schema = resolveSchema(flywayCustomConfiguration.getDefaultSchema());
String[] locationsArray = flywayCustomConfiguration.getLocations();
List<String> locations = Lists.newArrayList(locationsArray);
List<SystemVariable> systemVariables = systemVariableRepo.findAll();
String path = locationsArray[0];
// 文件名列表
List<String> fileNameList = ListUtil.of(new PathMatchingResourcePatternResolver().getResources(path + "/*")).stream()
.filter(resource -> resource.exists() && resource.getFilename() != null)
.map(Resource::getFilename)
.sorted()
.collect(Collectors.toList());
if (CollUtil.isNotEmpty(fileNameList)) {
locations.remove(0);
fileNameList.stream()
.filter(name -> name != null &&
(name.toLowerCase().contains(VERSION) ||
systemVariables.stream().anyMatch(var -> name.equals(var.getName()))))
.map(name -> path + "/" + name)
.forEach(locations::add);
}
// 创建 Flyway 实例并配置参数
Flyway.configure()
.dataSource(dataSource) // 数据源
.locations(locations.toArray(new String[0])) // 迁移脚本的路径
.createSchemas(flywayCustomConfiguration.getCreateSchemas()) // 是否创建架构
.cleanDisabled(flywayCustomConfiguration.getCleanDisabled()) // 是否禁用清理
.baselineOnMigrate(flywayCustomConfiguration.getBaselineOnMigrate()) // 在迁移时是否使用基线
.outOfOrder(flywayCustomConfiguration.getOutOfOrder()) // 是否允许脚本超出顺序
.schemas(schema) // 要求和defaultSchema一致
.defaultSchema(schema) // 默认 schema
.load().migrate();
ProfilingUtils.logTime(start, "Flyway 迁移完成【SQL脚本执行完成......】", null);
} catch (Exception e) {
log.error("Flyway 初始化失败,原因:{}", e.getMessage(), e);
throw new CustomException("Flyway 初始化过程中发生错误");
}
}
public String resolveSchema(String schema) {
DbTypeEnum dbType = SqlUtils.getDbType();
if (schema == null) return null;
return (dbType == DbTypeEnum.ORACLE || dbType == DbTypeEnum.DAMENG)
? schema.toUpperCase()
: schema;
}
}
4、sql脚本位置
📝 5、脚本命名规范(非常重要)
Flyway 通过文件名来识别版本和顺序,命名规范如下:
✅ 命名格式:
V版本号__描述.sql
✅ 示例:
V1__init_schema.sql
V2__add_user_table.sql
V3_1__add_index.sql (小版本)
-
V1
、V2
是版本号(不可重复) -
__
是两个下划线(不能少于两个) -
.sql
为 SQL 脚本 -
Java 脚本则用
*.java
但使用较少
⚠️ 6、使用注意事项
📌 脚本注意事项:
-
不可修改已执行的脚本:
因为 Flyway 会记录执行历史,一旦执行不能改内容,否则校验失败。 -
每个脚本只能表示一个逻辑版本:
避免一个脚本里做太多事情,不好排查错误。 -
不要跳过版本号:
不建议直接跳 V1 → V5,这可能引起迁移混乱。 -
脚本必须是幂等的(尽可能):
避免多次执行报错,适当使用IF NOT EXISTS
等。 -
字符集要统一(一般为 UTF-8)
7、数据库记录表(flyway_schema_history)
Flyway 会自动生成一个表来记录迁移历史,表名默认是:
flyway_schema_history
📌 8、Flyway 配置注意事项:
配置项 | 说明 |
---|---|
baseline-on-migrate | 对已有数据库初始化时设为 true (防止已有表报错) |
clean-disabled | 防止误删库结构,生产环境建议设为 true |
out-of-order | 是否允许乱序执行版本号,建议设为 false |
locations | 指定脚本目录,支持多目录 |