背景介绍
集成flyway有什么好处?
将 Flyway 集成到 Spring Boot 项目中有许多好处,特别是在管理数据库迁移和版本控制方面。以下是我在验证过程中从flyway官网、社区、有关技术博客了解和整理的主要优势:
- 简化开发流程,自动化数据库迁移:在应用启动时,Flyway 会自动执行所有未执行的迁移脚本,这使得数据库迁移变得非常简单和自动化。
- 减少手动操作:开发人员不再需要手动执行 SQL 脚本,减少了人为错误的可能性。统一版本化管理
- 上线版本与数据库变更同步: 每次上线的脚本交由java服务控制,发版启动时执行sql;如果sql执行失败,应用也会被禁止启动;
- 持续集成与部署:Flyway 可以与 CI/CD 管道无缝集成,每次代码更新时自动应用相应的数据库迁移。这使得在不同环境中的部署更加一致和可靠。并且Flyway 迁移脚本可以在开发、测试和生产环境中以相同的方式执行,确保每个环境中的数据库状态一致。
- 灵活的配置与扩展:Flyway 可以通过 Spring Boot 的配置文件(如 application.properties 或 application.yml)进行配置,支持各种定制化需求,如设置基准版本、忽略失败的迁移,自定义迁移策略等。
- 支持 Java API:除了使用 SQL 脚本,Flyway 还支持通过 Java API 编写迁移逻辑; 总之就是集成后大大滴方便(集成过程很抽象),有关Flyway集成的基础知识和有关概念放到了文章的概念解释章节中。
集成准备
个人环境记录(2024-08-14)
- spring boot版本:2.3.12.RELEASE
- flyway版本(依赖版本):8.2.0
- mysql版本:8.0 (mysql-connector-java: 8.0.28)
- druid连接池版本:1.1.23
确定集成方案
验证过程中发现flway支持多种方案,这里只罗列集成实践中最后采取的方案: 功能点验证汇总:
- 不同环境隔离开,实现环境独立性,确保不同环境(如开发、测试、生产)可以执行各自的 SQL 脚本 解决方案: 使用环境特定的 SQL 目录,nacos不同环境读取对应环境文件夹下的sql配置。
- 确保如果 SQL 执行出错,则整个脚本回滚,并且修改sql文件后支持重试机制(不用手动去改数据库) 解决方案: 将整个 SQL 文件的内容包裹在一个事务中,使用 BEGIN 和 COMMIT/ROLLBACK 语句。 自定义 FlywayMigrationStrategy,将 repair 操作与 Flyway 迁移操作结合在一起,并确保 repair 在迁移前执行。
调料
- n个sql文件用于模拟版本上传
集成步骤
项目准备
项目添加依赖、配置参数文件
添加maven依赖,注意打包配置
<!-- 8.2.0 -->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>${flyway.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
添加配置参数
# ———————————— flyway ————————————
spring.flyway.enabled=true
# flyway 的 clean 命令会删除指定 schema 下的所有 table, 所有环境都不能启用。
spring.flyway.clean-disabled=true
# SQL 脚本的目录,多个路径使用逗号分隔 默认值 classpath:db/migration
spring.flyway.locations=classpath:db/migration
# metadata 版本控制信息表 默认 flyway_schema_history
spring.flyway.table=flyway_schema_history
# 如果没有 flyway_schema_history 这个 metadata 表, 在执行 flyway migrate 命令之前, 必须先执行 flyway baseline 命令
# 设置为 true 后 flyway 将在需要 baseline 的时候, 自动执行一次 baseline。
spring.flyway.baseline-on-migrate=true
# 指定 baseline 的版本号,默认值为 1, 低于该版本号的 SQL 文件, migrate 时会被忽略
spring.flyway.baseline-version=1
# 字符编码 默认 UTF-8
spring.flyway.encoding=UTF-8
# 是否允许不按顺序迁移 强制按照顺序迁移: false
spring.flyway.out-of-order=false
# 需要 flyway 管控的 schema list,这里我们配置为flyway 缺省的话, 使用spring.datasource.url 配置的那个 schema,
# 可以指定多个schema, 但仅会在第一个schema下建立 metadata 表, 也仅在第一个schema应用migration sql 脚本.
# 但flyway Clean 命令会依次在这些schema下都执行一遍. 所以 确保生产 spring.flyway.clean-disabled 为 true
spring.flyway.schemas=
添加自定义迁移策略
import org.springframework.boot.autoconfigure.flyway.FlywayMigrationStrategy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/*******************************************************************
* <pre></pre> * @文件名称: FlywayConfiguration.java
* @包 路 径: com.szyx.tcm.web.manage.config
* @Copyright:wy (C) 2024 *
* @Description: 自定义Flyway配置 修复数据库
* @Version: V1.0
* @Author: wy
* @Date: 2024/8/14 14:04
* @Modify:
*/
@Configuration
public class FlywayConfiguration {
@Bean
public FlywayMigrationStrategy flywayMigrationStrategy() {
return flyway -> {
flyway.repair(); // 先执行repair命令
flyway.migrate(); // 然后执行迁移
};
}
}
在指定的locations中添加sql文件,注意命名

快捷生成版本sql文件
在 Maven 项目中,根目录的 pom.xml 通常用于管理和聚合子模块。在这种情况下,如果想在根目录运行一个 Java 文件,需要在根目录的 pom.xml 中添加一个模块专门用于运行 Java 代码,这是由于由于根目录的 pom.xml 是聚合和管理子模块的 pom 类型(pom),无法直接在这个 pom.xml 中编译和运行 Java 代码。需要创建一个新的子模块,专门用于包含和运行需要的 Java 代码。
对于多模块的maven项目,这里提供一个基于根目录访问新建sql文件的工具类,控制台输入版本和分支可以快速生成一个版本的sql文件,和用到的模版文件
新建sql文件的工具类 FlywayInitFileGenerator
import java.io.*;
import java.util.Scanner;
/*******************************************************************
* <pre></pre> * @文件名称: FlywayInitFileGenerator.java
* @包 路 径: com.szyx.tcm.base.web
* @Copyright:wy (C) 2024 *
* @Description: 全局创建flyway初始化文件,用于每次拉取新分支后,生成新的初始化文件,包括base web服务、manage、patient、medical、epitaxy、external服务
* @Version: V1.0
* @Author: wy
* @Date: 2024/8/14 17:01
* @Modify:
*/
public class FlywayInitFileGenerator {
final static String LOCATIONS = "db/migration";
final static String[] DOMAINS = {"dev", "test", "prod"};
final static String TEMPLATE_FILE_NAME = "flyway-init-file-template.sql";
final static String BASE_WEB_PATH = "服务的启动模块项目路径/src/main/resources";
final static String MANAGE_PATH = "服务的启动模块项目路径/src/main/resources";
// ........ 可以自己添加
public static void main(String[] args) {
String version = "";
String branchName = "";
Scanner scanner = new Scanner(System.in);
System.out.println("请输入需要生成的flyway统一的版本号 注意请以 Vx.x.x的 格式:");
version = scanner.nextLine();
System.out.println("请输入分支名称:");
branchName = scanner.nextLine();
System.out.println("分支名称:" + branchName + ",开始生成flyway初始化文件");
if (version == null || version.isEmpty()) {
throw new RuntimeException("版本号不能为空");
}
if (branchName == null || branchName.isEmpty()) {
throw new RuntimeException("分支名称不能为空");
}
// String[] paths = {BASE_WEB_PATH, MANAGE_PATH, PATIENT_PATH, MEDICAL_PATH, EPITAXY_PATH, EXTERNAL_PATH};
String[] paths = {BASE_WEB_PATH,MANAGE_PATH};
String projectRoot = System.getProperty("user.dir");
generateFlywayInitFile(paths, version, branchName, projectRoot);
System.out.println("flyway初始化文件生成完成!");
}
/**
* 生成flyway初始化文件
*
* @param paths 需要生成版本控制文件的项目路径
* @param version 版本号
* @param branchName 分支名称
* @param projectRoot 项目根路径
*/
private static void generateFlywayInitFile(String[] paths, String version, String branchName, String projectRoot) {
// 使用 ClassLoader 读取模板文件
ClassLoader classLoader = FlywayInitFileGenerator.class.getClassLoader();
try (InputStream inputStream = classLoader.getResourceAsStream(TEMPLATE_FILE_NAME)) {
if (inputStream == null) {
System.err.println("Template file not found in resources!");
return;
}
// 使用 BufferedInputStream 读取模板文件
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
// 标记当前位置
bufferedInputStream.mark(Integer.MAX_VALUE);
for (int i = 0; i < DOMAINS.length; i++) {
String newSqlDocumentFileName = version + "__" + branchName + "-" + DOMAINS[i] + ".sql";
for (String path : paths) {
String modulePath = projectRoot + "/" + path + "/" + LOCATIONS + "/" + DOMAINS[i];
File dirModulePath = new File(modulePath);
if (!dirModulePath.exists()) {
dirModulePath.mkdirs();
}
String newFilePath = modulePath + "/" + newSqlDocumentFileName;
// 将模板文件复制到目标文件
try (OutputStream outputStream = new FileOutputStream(newFilePath)) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = bufferedInputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
System.out.println("File created: " + newFilePath);
} catch (IOException e) {
e.printStackTrace();
}
// 重置输入流 以便下次读取
bufferedInputStream.reset();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
模版文件 flyway-init-file-template.sql
/* start */
BEGIN;
/* ===================================== 注意所有需要执行的sql写到 该注释下 ===================================== */
/* ===================================== 注意所有需要执行的sql不能超过 该注释 ===================================== */
/* Single line comment */
COMMIT;
/* Single line comment */
ROLLBACK;
验证集成功能
启动服务,flyway会自动在指定的数据库创建用于记录版本迁移情况的表。

初次创建并且开启了基线版本,由于flyway的版本号校验十分严格,老脚本发生了变化后checkSum校验就会导致启动失败,新增的脚本则会和数据库中的版本号进行比较,如果小于数据库存储的最后一个版本号,也不会成功,所以要求开发人员严格维护脚本的版本号。
每次按照版本维护和更新文件,执行后就可以了。
概念、踩坑经验记录
Flyway 版本兼容问题
flyway目前有多个版本(v5~v10),值得注意的是,虽然flyway是一个Apache License2的开源项目,但是官网的使用文档中是可以看到flyway是有社区版本、团队版本、企业版本、定制版本、进阶版各种版本(刀法精湛);
总体上来看,以mysql为例,开源免费的社区版本支持能力有限,所以找一个稳定支持的版本还得根据实际项目的情况来集成。因此可以看出flyway的兼容性还是挺差的,对于白嫖党来说,可能得找一个稳定的版本使用。

flyway目前有多个版本(v5~v10)除了对数据库的版本兼容存在问题还对jdk版本存在兼容问题。

并且由于版本众多,官方文档中对于开源版本兼容的解释很模糊,虽然springboot中有集成flyway依赖,但是建议还是根据项目的实践情况和需求进行选择(有的版本居然sql文件不支持带注释)

本着探索支持jdk8并且支持mysql8.0并且是flyway高版本💀,花了一天在Qiita上看到了一个日本老哥的gradle配置文件,找到了8.2.0

Flyway使用注意事项
迁移脚本执行顺序与事务处理:
V 文件(版本迁移文件)的优先级高于 R 文件(修复脚本),如果一个 V 文件和多个 R 文件同时执行,其中一个 V 文件报错,V 文件仍会完成执行,并记录到 flyway_schema_history,而 R 文件不会执行也不会记录。
删除表中的报错记录后,再次启动时,Flyway 会重新执行原错误的 V 文件和未执行的 R 文件。V 文件的执行顺序是基于版本号的顺序,Flyway 依赖文件命名来确定执行的顺序,因此建议严格遵守版本命名规范。为了避免出错前的部分 SQL 被执行,建议将所有需要回滚的操作都包含在一个事务中
多个 R 文件的处理: 如果多个 R 文件中有一个出现错误,那么其后的 R 文件将不会被执行。避免在数据库状态不一致时执行后续操作。需要注意,R 文件的执行顺序同样基于命名,因此在修复时要特别小心。
单个文件中的 DDL 和事务管理:
在一个迁移文件中,如果包含多个 DDL 语句,Flyway 不会将其作为一个事务来管理。例如,如果文件中有三个创建表的语句,其中一个语句报错,第一个表仍会被创建且提交事务。
这意味着在设计迁移脚本时,应该避免将多个独立的 DDL 语句放在同一个文件中,或者明确知道这些语句不会回滚。
DML 语句的回滚:
如果同一个迁移文件下的 DML 语句(如 INSERT, DELETE, UPDATE)在执行中出错,那么所有的 DML 语句都会回滚。
这是因为 DML 语句通常被包装在事务中执行,而事务失败会导致所有更改被回滚。确保在关键操作前验证数据的正确性以减少回滚的可能性。
已执行的 V 文件不可修改:
已经执行过的 V 文件不能被修改,否则启动时会报错。 Flyway 会对 V 文件进行校验,任何修改都会被检测到并阻止应用启动。
如果必须对已经执行的迁移进行修改,建议新建一个新的迁移文件并进行补丁处理,而不是修改已存在的文件。
版本号冲突的处理: 如果存在多个迁移文件的版本号相同,会导致报错(例如出现多个 1.0.0.9 版本的迁移文件)。为避免版本号冲突,最好在团队中约定好迁移文件的版本命名规则,并且在提交迁移文件之前检查是否有相同版本号的文件存在。
删除 SQL 文件后的处理: 删除迁移 SQL 文件后再启动应用时,如果 Flyway 检测到缺失的迁移文件,也会报错。Flyway 依赖于 flyway_schema_history 表中的记录来追踪已经执行的迁移,如果迁移文件被删除,必须使用 flyway repair 来修复记录,或者重新生成丢失的迁移文件。