Flyway-数据库的“版本控制系统” 软件开发的过程中数据库变更的管理是件容易让人头疼的事情。尤其在多人协作和多环境部署的场景下一个遗漏的索引、一处不一致的表结构都可能让应用启动失败引发线上问题。而Flyway就和 Git 管理代码类似他为数据库的变更提供了一个可靠的“版本控制系统”。1. Flyway 介绍1.1. 基本概念Flyway是一款专注于数据库版本控制和自动化迁移的开源工具。它的核心理念可以类比为数据库界的“Git”它使用版本化的SQL脚本帮助开发和管理团队在不同环境如开发、测试、生产中自动化、可靠地同步数据库结构变更1.2. 工作原理1.2.1. 版本化迁移脚本这是Flyway的核心每个数据库变更都对应一个独立的SQL脚本文件。命名必须遵循严格的规范以便Flyway识别和排序。命名规范V版本号__描述.sql。例如V1__Create_user_table.sql,V2.1__Add_email_column.sql。注意这里的版本号与描述是两个下划线组成的。版本号支持整数、点分版本号如1.0.1甚至是时间戳。执行顺序严格由版本号升序决定。内容在脚本内部你可以安全地编写所有DDL和DML语句。1.2.2. 元数据表Flyway会在你的数据库中创建并维护一张表默认名称为flyway_schema_history用来记录所有已执行过的迁移脚本的详细信息如版本号、脚本名、校验和Checksum、执行状态、执行耗时等。1.2.3.执行流程应用启动时Flyway会扫描类路径下指定的迁移脚本目录。它连接数据库读取flyway_schema_history表获取当前数据库的版本状态和已应用脚本的校验和。校验它会对比每个已应用脚本的当前内容和历史记录中的校验和确保脚本没有被意外修改。迁移如果校验无误Flyway会找出所有版本号大于当前数据库版本且尚未执行的脚本然后按版本号升序依次执行。每个脚本执行成功后Flyway会在历史表中记录一条新记录并更新数据库的版本号2. 集成 SpringBoot当前集成环境JDK 17SpringBoot 3.5.14MySQL 82.1. 引入依赖dependency groupIdorg.flywaydb/groupId artifactIdflyway-core/artifactId version12.8.1/version scopecompile/scope /dependency dependency groupIdorg.flywaydb/groupId artifactIdflyway-mysql/artifactId version12.8.1/version scopecompile/scope /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-jdbc/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency2.2. 配置文件配置文件中简单介绍了几个配置项spring: # 数据库连接 datasource: url: jdbc:mysql://127.0.0.1:3306/db1 username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver # Flyway 配置 flyway: # 是否开启自动装配默认为 true enabled: true # flyway迁移脚本路径默认为 classpath:db/migration locations: classpath:db/mysql # 是否禁止清理执行flyway的清理功能会把整个数据库清空可在本地环境使用线上环境一定要禁用默认值为false # clean-disabled: false # SQL 迁移脚本的前缀、分隔符和后缀 # sql-migration-prefix: V # sql-migration-separator: __ # sql-migration-suffixes: .sql # 是否跳过错误默认为false # error-overrides: false # 配置的脚本路径不存在时是否抛出异常 # fail-on-missing-locations: false # 你有一个已经上线运行的数据库里面已经有表和数据 # 现在想引入 Flyway 来管理后续的数据库变更。此时 # 已有的表是 V1.0.1 之前手动建的 # 设置 baseline-on-migrate: true 后 # Flyway 会在 flyway_schema_history 中标记一条 baseline 记录版本 1 # 之后只执行版本号 1 的迁移脚本如 V1.0.1、V1.0.2不会重新执行已有的建表逻辑 # baseline-on-migrate: false # baseline-version: 1 # 开启flyway的执行日志 logging: level: org: flywaydb: debug2.3. 编写 SQL 脚本在资源文件夹下按自己的喜好创建脚本文件夹这个路径一定要与配置文件中的路径匹配spring.flyway.locations新建脚本文件V1.0.1__create_books.sql这里第一个脚本文件的版本号命名以1.0.1开始因为 flyway 配置中基准线 baseline-version默认为 1代表 flyway 只会去扫描版本号大于 1 的。CREATE TABLE books ( id int(11) NOT NULL AUTO_INCREMENT COMMENT 书籍唯一ID, title varchar(255) NOT NULL COMMENT 书名, author varchar(100) DEFAULT NULL COMMENT 作者, isbn varchar(20) DEFAULT NULL COMMENT 国际标准书号, price decimal(10,2) DEFAULT NULL COMMENT 价格元, publication_date date DEFAULT NULL COMMENT 出版日期, publisher varchar(100) DEFAULT NULL COMMENT 出版社, pages int(8) DEFAULT NULL COMMENT 页数, category varchar(50) DEFAULT NULL COMMENT 分类如小说、科技等, description text COMMENT 简介, created_at datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), UNIQUE KEY uk_isbn (isbn), KEY idx_title (title), KEY idx_author (author) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT书籍信息表;2.4. 项目启动接下来启动项目即可看到脚本自动执行其中flyway 会在数据库中新建一个 flyway_schema_history 表里面记录了各个脚本的执行情况。3. 找茬测试陆续添加了两个脚本文件分别启动项目让 sql 生效V1.0.2__addData.sqlINSERT INTO books (title, author, isbn, price, publication_date, publisher, pages, category, description) VALUES (深入理解Java虚拟机, 周志明, 9787121327659, 79.80, 2019-11-01, 机械工业出版社, 500, 编程, JVM经典书籍全面讲解Java虚拟机内存管理、类加载机制等。), (三体黑暗森林, 刘慈欣, 9787536692937, 32.00, 2008-05-01, 重庆出版社, 470, 科幻, 三体系列第二部讲述黑暗森林法则。), (Python编程从入门到实践, Eric Matthes, 9787115428028, 89.00, 2016-07-01, 人民邮电出版社, 476, 编程, Python入门经典包含基础知识和实战项目。), (活着, 余华, 9787506365437, 28.00, 2012-08-01, 作家出版社, 191, 小说, 讲述普通人在大时代背景下的命运浮沉。);V1.0.3__deleteData.sqldelete from books where title 深入理解Java虚拟机;3.1. 版本号一致 1新建一个脚本文件命名为V1.0.3__addData.sql这个文件名的版本前缀与V1.0.3__deleteData.sql是一致的但是V1.0.3__deleteData.sql已经被执行过V1.0.3__addData.sql还没执行这时候启动项目会怎么样直接报错版本冲突数据库中也没有记录这个新脚本的执行情况3.2. 版本号一致 2这次尝试将两个未执行过的脚本取一样的版本名前缀看一下效果如何如下图所示依旧报错版本冲突并且数据库记录中没有 1.0.4 版本的执行记录但是按执行顺序V1.0.4__addData.sql是可以执行成功的只是V1.0.4__deleteData.sql失败了但最终是都失败这与 fireway 的事务性的执行有关一起成功一起失败。4. 遇到的问题4.1. 依赖缺少 jdbc在依赖中必须添加 jdbc 的依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-jdbc/artifactId /dependency如果没有添加启动项目时Flyway 不会被执行原因Spring Boot 的 Flyway 自动配置FlywayAutoConfiguration依赖于 DataSource Bean 的存在。而 DataSource Bean 是由 DataSourceAutoConfiguration 创建的这个自动配置来自 spring-boot-starter-jdbc或 spring-boot-starter-data-jpa。当前的依赖中有 flyway-core — Flyway 库本身有 mysql-connector-j — JDBC 驱动但没有 spring-boot-starter-jdbc — 所以没有 DataSource Bean 被创建Flyway 自动配置的条件不满足直接跳过了4.2. 依赖缺少flyway-mysqldependency groupIdorg.flywaydb/groupId artifactIdflyway-mysql/artifactId version12.8.1/version scopecompile/scope /dependency如果没有添加则会报错Unsupported Database: MySQL 8.0原因从 Flyway 10.x 开始MySQL 的数据库支持被拆分到了独立的模块 flyway-mysql 中。你的项目只引入了 flyway-core缺少 MySQL 支持模块4.3. Flyway 版本划分存在未开源的功能在摸索 Flyway 的配置时我在 demo 项目里配置文件里加入了一些 flyway 配置参数一启动就报错了后面根据报错查了 AI 发现是 Flyway 存在版本划分就是一些功能配置不开源使用。以下是 AI 总结的版本划分表仅供参考。版本名称费用Community社区版开源免费Pro专业版付费Enterprise企业版付费最高级社区版免费支持的配置——基础迁移功能相关的配置如locations、baseline-on-migrate、clean-disabledsql-migration-prefix、sql-migration-separatorvalidate-on-migrate、out-of-order基础的flyway migrate、flyway validate、flyway clean商业版Pro/Enterprise独有的配置和功能功能说明Undo migrations回滚迁移flyway undo需要U前缀的回滚脚本Callbacks高级支持 Java/SQL 回调在迁移生命周期中插入自定义逻辑ScriptConfig files支持脚本级参数替换占位符从外部文件注入Oracle/DB2 SQL*Plus 支持sqlplus语法兼容Dry Run试运行生成迁移预览 SQL 文件不实际执行Teams/Schema 对比对比两个 schema 的差异并生成迁移脚本Pipeline 集成CI/CD 集成的高级功能Reports生成迁移报告4.4. 脚本执行失败后遗症在测试 Flyway 的脚本运行过程中遇到一次脚本执行失败的情况失败原因是 SQL 语法的错误我修正 SQL 语法后再运行依旧是启动报错的。重点就是 Detected failed migration to version 1.0.4 (addData). Please remove any half-completed changes then run repair to fix the schema history观察数据库表保存了一条失败记录Flyway 要求一旦某个版本的执行失败必须先清理历史记录才能重新运行迁移。不能直接重启应用。清理历史记录的方法有直接删除flyway_schema_history表中对应执行失败的数据库记录最直接使用 Maven 插件执行 repair ——mvn flyway:repair使用 Flyway 命令行工具 ——flyway repair需要提前下载并配置5. 多数据源的情况一个项目可能会存在多个数据库的情况下面我就用 MySQL 和 PG 数据库来作为案例。首先在项目中添加依赖dependency groupIdorg.postgresql/groupId artifactIdpostgresql/artifactId scoperuntime/scope /dependency !-- dynamic-datasource 是一款开源的 Spring Boot 多数据源启动器。 它的核心价值在于通过简单的注解就能让你在项目中优雅、无侵入地动态切换数据源 从而将开发者从繁琐的多数据源底层管理工作中解放出来 -- dependency groupIdcom.baomidou/groupId artifactIddynamic-datasource-spring-boot3-starter/artifactId version4.5.0/version scopecompile/scope /dependency修改配置文件注意这里需要将 flyway 的自动装配关闭我们需要通过代码来定制化执行。我这里换了一个新的 MySQL 数据库为了直接复用前面脚本spring: application: name: tools-flyway datasource: dynamic: primary: mysql datasource: mysql: url: jdbc:mysql://localhost:3306/db2 username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver postgresql: url: jdbc:postgresql://localhost:5432/db1 username: postgres password:your_password driver-class-name: org.postgresql.Driver # Flyway 配置 flyway: # 是否开启自动装配默认为 true enabled: false # 开启flyway的执行日志 logging: level: org: flywaydb: debug新建一个 config 包创建一个FlywayDynamicDataSourceConfig类/** * Flyway 多数据源动态迁移配置 * p * 监听应用就绪事件遍历 dynamic-datasource 注册的所有数据源 * 为每个数据源独立执行 Flyway 迁移实现多库版本管理的自动化。 * p * 迁移脚本路径约定classpath:db/{数据源名称}/ * 历史记录表命名约定flyway_schema_history_{数据源名称} */ Configuration public class FlywayDynamicDataSourceConfig implements ApplicationListenerApplicationReadyEvent { /** * 注入由 dynamic-datasource 管理的动态路由数据源 */ Resource private DataSource dataSource; /** * 应用就绪后触发所有数据源的 Flyway 迁移 * p * 执行流程 * 1. 将 DataSource 强转为 DynamicRoutingDataSource获取全部已注册的数据源 * 2. 遍历每个数据源根据其 poolName 加载对应的迁移脚本目录 * 3. 为每个数据源创建独立的历史记录表避免多库记录冲突 * 4. 开启 baselineOnMigrate对已有数据库自动设置基线版本 * * param event 应用就绪事件 */ Override public void onApplicationEvent(ApplicationReadyEvent event) { // 获取动态路由数据源实例 DynamicRoutingDataSource dynamicRoutingDataSource (DynamicRoutingDataSource) dataSource; // 获取所有已注册的数据源映射keypoolName, valueDataSource MapString, DataSource dataSources dynamicRoutingDataSource.getDataSources(); for (Map.EntryString, DataSource entry : dataSources.entrySet()) { String poolName entry.getKey(); Flyway.configure() .dataSource(entry.getValue()) // 指定当前数据源 .locations(classpath:db/ poolName) // 迁移脚本目录db/{poolName}/ .table(flyway_schema_history_ poolName) // 独立历史记录表防止多库冲突 .baselineOnMigrate(true) // 已有数据库自动设置基线无需手动初始化 .load() .migrate(); // 执行迁移 System.out.println(Flyway migration completed for datasource: poolName); } } }与配置文件对应的脚本文件夹启动项目效果1、MySQL 的四个脚本均执行成功2、PG 库的脚本也执行成功6. 比较难受的地方项目开发的使用下来感觉有两点是这个工具比较难受的地方团队开发过程中的版本命名冲突比如开发者A 与 B 同时拉取了最新的代码都基于最近的代码进行开发他们两个碰到的需求都需要对表操作也就是都需要添加数据库脚本这时他们看最新的代码发现版本号命名到了 V1.0.98__xxx.sql他们就自然地命名各自地脚本为V1.0.99__xxxA.sql 与V1.0.99__xxxB.sql这时就出现了两个 V1.0.99就像上文的找茬测试中所表现的这样就出问题了需要开发者上推代码后细节核对。类似情况还有两个人创建的不同 sql 脚本但改了同一个表的同一个字段而产生冲突的情况这在团队开发中好像不可避免。文件夹的肿胀数据库的每次更改都伴随着一个 sql 脚本随着开发到后期存放 sql 脚本的文件夹就会很肿胀每次在 IDEA 里打开脚本文件夹都要下拉拉很久。