Flyway
Database Migrations Made Easy.
介绍
Flyway是一款数据库迁移(migration)工具,在你部署应用的时候,帮你执行数据库脚本的工具,Flyway支持SQL和Java两种类型的脚本,你可以将脚本打包到应用程序中,在应用程序启动时,由Flyway来管理这些脚本的执行,这些脚本被Flyway称之为Migration。
特点
- 简单 非常容易安装和学习,同时迁移的方式也很容易被开发者接受。
- 专一 Flyway 专注于搞数据库迁移、版本控制而并没有其它副作用。
- 强大 专为连续交付而设计。让Flyway在应用程序启动时迁移数据库。
软件架构
SpringBoot + Flyway + 支持 (Mysql、Oracle、DM(达梦)、PG、sqlServer等关系型数据库)
使用说明
- 添加脚本db/migration文件下根据顺序新增。
- 如果设计文档或者特殊数据处理可以在db.migration包下使用JAVA代码执行。
- 版本号命名:如果在db/migration下的版本号那么在db.migration下递增版本号,反之在db.migration有了版本号在db/migration也是递增。
- 执行过的脚本不能再次执行需要另行加新版本修改执行即可。
- 参考范例自行扩展。
Flyway 中的常用概念
Flyway 中的概念可查阅 官方文档,这里挑选一些重要的进行简单介绍。
Schema History Table
Flyway 对数据库进行版本控制的方式,是在指定数据库中创建一张表,即 Schema History Table(默认为 flyway_schema_history
),记录由 Flyway 所执行的 sql 脚本状态。
Migration(核心)
在 Flyway 中,所有对数据库的变动,均称为 migration,migration 可以是 SQL 文件,也可以是 Java 类。
默认的查找 migration 的路径为 classpath:db/migration
,对应 SQL 文件可放置在 src/main/resources/db/migration
下,Java 类可放置在 src/main/java/db/migration
下。
Migration 可以是仅执行一次的(versioned),也可是重复执行的(repeatable)。
Versioned Migration
1、Versioned migration 包括版本号、描述和校验值(checksum,自动计算),命名方式如下:
其中版本号必须全局(一个 Schema History Table 里)唯一,且默认情况下(可通过参数调整)版本号只能增加,不能在已经执行了高版本的 migration 之后再执行低版本的 migration。
注意点:sql脚本与java类,共享版本号。
2、版本号可以是数字加 .
的形式,例如下列都是合法的版本号:
1
001
5.2
1.2.3.4.5.6.7.8.9
205.68
20130115113556
2013.1.15.11.35.56
2013.01.15.11.35.56
3、Flyway 是如何比较两个 SQL 文件的先后顺序呢?它采用 采用左对齐原则, 缺位用 0 代替 。举几个例子:
1.0.1.1 比 1.0.1 版本高。
1.0.10 比 1.0.9.4 版本高。
1.0.10 和 1.0.010 版本号一样高, 每个版本号部分的前导 0 会被忽略。
了解
checksum
用来检查 migration 在执行过后是否发生了变化,如果发生了变化,会导致这个版本以及后续版本的 migration 无法执行。
Repeatable Migration
Repeatable migration 包括描述和校验值,没有版本号信息,会在每次校验值(migration 内容变化会导致校验值变化)发生变化时重复执行,命名方式如下:
Repeatable migration 会在所有的 versioned migration 都执行过后再进行执行,Repeatable migration 内部按照其文件名中描述(description)部分的顺序执行。
在 repeatable migration 中需保证其中内容可重复执行,比如在 DDL 中使用 CREATE OR REPLACE
。
Baseline
当数据中已经存在内容时,再引入 Flyway,可通过 Baseline 设定一条基线。
如果想将基线之前的数据库中表结构和数据纳入 Flyway 一同管理,可以将基线前的状态导出成数据库脚本,通过 versioned migration 添加至 flyway 中,并设定 baselin version,Flyway 会忽略基线版本号(包括)之前版本的所有 migration。
如果不想管理基线之前的数据库状态(比如多模块或应用操作同一数据库,互相之间不受影响),可以只告诉 Flyway 执行 migration 的时候是存在基线的,这样就不会报出数据库非空的异常。
如何在一个 Spring Boot 项目中引入 Flyway
在 Spring Boot 项目中,引入 Flyway 非常简单,因为在 Spring Boot 的 spring-boot-autoconfigure 中包含了 Flyway 的自动配置,只要添加 flyway 的依赖即可。开箱即用
org.flywaydb flyway-core
添加 Flyway 依赖后,无需其他配置,Flyway 的功能是自动启用的,如果想停用 Flyway,需设置 spring.flyway.enabled=false
。
添加 sql 脚本
引入 Flyway 之后,再启动应用时,会在默认路径 classpath:db/migration
查找 sql 脚本,如果没找到会报错,影响应用启动。
可在 src/main/resources/db/migration
路径下创建 sql 文件,如 src/main/resources/db/migration/V20220530__test.sql
,使应用可正常启动。
启动成功后,可在所指定的数据库中的 flyway_schema_history
表中查看初始化脚本执行状态。
可通过 spring.flyway.table=another_table
修改默认表名。
修改默认表名,可实现多个模块分别通过 Flyway 进行数据库版本控制,并连接到相同数据库的效果。
非空数据库处理
当在一个已有项目中引入 Flyway 时,数据库中可能已经存在了一些表和初始化数据,此时按照上述方式引入 Flyway 时会提示如下异常:
TEXT1org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'flywayInitializer' defined in class path resource [org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration$FlywayConfiguration.class]: Invocation of init method failed; nested exception is org.flywaydb.core.api.FlywayException: Found non-empty schema(s) `demo` but no schema history table. Use baseline() or set baselineOnMigrate to true to initialize the schema history table.
此时需要设定 spring.flyway.baseline-on-migrate=true
参数,告诉 Flyway 在执行 sql 脚本之前,数据库是非空状态。
如果想指定 baseline version,可通过设定 spring.flyway.baseline-version=20210809
参数,以忽略 20210809
版本以及之前的所有 migration。
其他问题
执行方式
Flyway 的 migration 会在 Spring Boot 应用启动时自动执行,如果不想通过启动应用的方式执行,官方提供了命令行、API、以及 Maven 和 Gradle 插件的方式,但总的来说都会麻烦一些,因为需要将已经在 Spring Boot 中配置的参数,再到其他执行方式所各自要求的位置重新配置一遍,实用性一般。
生产环境数据安全性
Flyway 的 Clean 命令,会将 Flyway 所连接的数据库中的所有内容全部清理掉,不论其中的表或数据是否是通过 Flyway 添加进去的。
在生产环境中使用 Flyway 确实也存在一定的风险,但这个风险不是 Flyway 本身造成的,有权连接生产库的人的任何一个误操作,都会导致生产环境数据的丢失,建议不要因噎废食。
Flyway 对此也提供了一定的防范措施,可通过禁用 Clean 命令来防止此问题发生,比如通过 Spring Boot 的 spring.flyway.clean-disabled=true
参数。
建议在所有环境都禁用 Clean 命令。
SQL 报错
通过 Spring Boot 自动执行 migration 时要注意,一旦 migration 执行失败,应用启动会终止。出现 migration 执行失败时,需要将 Schema History Table 表中的失败记录处理掉,才能再次执行 migration,否则应用会一直无法启动。
out-of-order
多人开发时,可能会出现 A 写了 V1 脚本,B 写了 V2 脚本,B 的代码先合并进去了,V2 脚本先执行了,此时 A 的 V1 脚本受版本号只能增加的要求不能再执行。
这种情况可以通过将 spring.flyway.out-of-order
设置为 true
来暂时取消这个限制(不推荐 ),
强烈建议 A 将 V1 脚本版本号改为 V3。
使用姿势
flyway⽂件只能追加,不能修改⽂件本⾝,如果之前flyway添加的数据有问题,需要新添加flyway⽂件然后再⽂件中执⾏脚本删除之前的有问题数据。
每次新建flyway⽂件需要再本地dev环境运⾏成功后才能push。
Flyway配置释义:
对执行迁移时基准版本的描述.
flyway.baseline-description
当迁移时发现目标schema非空,而且带有没有元数据的表时,是否自动执行基准迁移,默认false.
flyway.baseline-on-migrate
开始执行基准迁移时对现有的schema的版本打标签,默认值为1.
flyway.baseline-version
检查迁移脚本的位置是否存在,默认false.
flyway.check-location
当发现校验错误时是否自动调用clean,默认false.
flyway.clean-on-validation-error
是否开启flywary,默认true.
flyway.enabled
设置迁移时的编码,默认UTF-8.
flyway.encoding
当读取元数据表时是否忽略错误的迁移,默认false.
flyway.ignore-failed-future-migration
当初始化好连接时要执行的SQL.
flyway.init-sqls
迁移脚本的位置,默认db/migration.
flyway.locations
是否允许无序的迁移,默认false.
flyway.out-of-order
目标数据库的密码.
flyway.password
设置每个placeholder的前缀,默认${.
flyway.placeholder-prefix
是否要被替换,默认true.
flyway.placeholder-replacementplaceholders
设置每个placeholder的后缀,默认}.
flyway.placeholder-suffix
设置placeholder的value
flyway.placeholders.[placeholder name]
设定需要flywary迁移的schema,大小写敏感,默认为连接默认的schema.
flyway.schemas
迁移文件的前缀,默认为V.
flyway.sql-migration-prefix
迁移脚本的文件名分隔符,默认__
flyway.sql-migration-separator
迁移脚本的后缀,默认为.sql
flyway.sql-migration-suffix
使用的元数据表名,默认为schema_version
flyway.tableflyway
迁移时使用的目标版本,默认为latest version
flyway.target
迁移时使用的JDBC URL,如果没有指定的话,将使用配置的主数据源
flyway.url
迁移数据库的用户名
flyway.user
迁移时是否校验,默认为true.
flyway.validate-on-migrate
spring:
# 一般配置
flyway:
# 禁用clean,默认false (重要!)
clean-disabled: true
# 是否启用flyway,默认true (重要)
enabled: true
# 当迁移发现数据库非空且存在没有元数据的表时,自动执行基准迁移,新建schema_version表,默认true (重要)
baseline-on-migrate: true
# 是否允许无序迁移,默认false
out-of-order: false
# 编码格式,默认UTF-8
encoding: UTF-8
# 迁移sql脚本文件存放路径,默认db/migration
locations: classpath:db/migration
# 迁移sql脚本文件名称的前缀,默认V
sql-migration-prefix: V
# 迁移sql脚本文件名称的分隔符,默认2个下划线__
sql-migration-separator: __
# 迁移sql脚本文件名称的后缀
sql-migration-suffixes: .sql
# 迁移时是否进行校验,默认true
validate-on-migrate: true