在 Spring Boot 中恢复 Flyway 失败的迁移

1、概览

Flyway 迁移并不总是一帆风顺行,本文将带你了解迁移失败后的恢复方案。

2、设置

从基本的 Spring Boot 配置 Flyway 开始。它依赖 flyway-corespring-boot-starter-jdbcflyway-maven-plugin

对于如何在 Spring Boot 中使用 Flyway 进行数据库迁移,你可以参阅 这篇文章

2.1、配置

首先,添加两个不同的 Profile(配置文件)。这能够轻松地针对不同的数据库引擎运行迁移:

<profile>
    <id>h2</id>
    <activation>
        <activeByDefault>true</activeByDefault>
    </activation>
    <dependencies>
        <dependency>
            <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        </dependency>
    </dependencies>
</profile>
<profile>
    <id>postgre</id>
    <dependencies>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
        </dependency>
    </dependencies>
</profile>

还要为每个 Profile 添加 Flyway 数据库配置文件。

首先,创建 application-h2.properties

flyway.url=jdbc:h2:file:./testdb;DB_CLOSE_ON_EXIT=FALSE;AUTO_RECONNECT=TRUE;MODE=MySQL;DATABASE_TO_UPPER=false;
flyway.user=testuser
flyway.password=password

然后,创建 PostgreSQL application-postgre.properties

flyway.url=jdbc:postgresql://127.0.0.1:5431/testdb
flyway.user=testuser
flyway.password=password

注:你可以调整 PostgreSQL 配置,使其与你的数据库相匹配,也可以使用 代码示例中的 docker-compose 文件

2.2、迁移

添加第一个迁移(migration)文件 V1_0__add_table.sql

create table table_one (
  id numeric primary key
);

添加第二个包含错误的迁移文件 V1_1__add_table.sql

create table table_one (
  id numeric primary key
);

这里故意犯了一个错误,使用了相同的表名。这会导致 Flyway 迁移错误。

3、运行迁移

现在,运行应用并尝试进行迁移。

首先是默认的 h2 Profile:

mvn spring-boot:run

然后是 postgre Profile:

mvn spring-boot:run -Ppostgre

不出所料,第一次迁移成功,而第二次迁移失败:

Migration V1_1__add_table.sql failed
...
Message    : Table "TABLE_ONE" already exists; SQL statement:

3.1、检查状态

在进行修复之前,通过运行以下命令检查 Flyway 迁移状态:

mvn flyway:info -Ph2

返回的结果如期:

+-----------+---------+-------------+------+---------------------+---------+
| Category  | Version | Description | Type | Installed On        | State   |
+-----------+---------+-------------+------+---------------------+---------+
| Versioned | 1.0     | add table   | SQL  | 2020-07-17 12:57:35 | Success |
| Versioned | 1.1     | add table   | SQL  | 2020-07-17 12:57:35 | Failed  |
+-----------+---------+-------------+------+---------------------+---------+

但是当使用以下命令检查 PostgreSQL 的状态时:

mvn flyway:info -Ppostgre

可以看到,第二次迁移的状态是 “Pending”,而不是 “Failed”:

+-----------+---------+-------------+------+---------------------+---------+
| Category  | Version | Description | Type | Installed On        | State   |
+-----------+---------+-------------+------+---------------------+---------+
| Versioned | 1.0     | add table   | SQL  | 2020-07-17 12:57:48 | Success |
| Versioned | 1.1     | add table   | SQL  |                     | Pending |
+-----------+---------+-------------+------+---------------------+---------+

不同之处在于,PostgreSQL 支持 DDL 事务,而 H2 或 MySQL 等其他系统则不支持。因此,PostgreSQL 可以回滚迁移失败的事务。

接下来,让我们看看当尝试修复数据库时,这种差异会产生什么影响。

3.2、纠正错误并重新运行迁移

修复迁移文件 V1_1__add_table.sql,将表名从 table_one 更正为 table_two

们再次尝试运行应用:

mvn spring-boot:run -Ph2

H2 迁移还是失败,原因如下:

Validate failed: 
Detected failed migration to version 1.1 (add table)

只要 version 1.1 的迁移已经失败,Flyway 就不会重新运行该版本的迁移。

postgre Profile 运行成功。如前所述,由于进行了回滚,状态是干净的,可以应用修正后的迁移。

事实上,通过运行 mvn flyway:info -Ppostgre 你可以看到两次迁移都成功了。因此,对于 PostgreSQL 来说,所要做的就是修改迁移脚本并重新触发迁移。

4、手动修复数据库状态

修复数据库状态的第一种方法是从 flyway_schema_history 表中手动删除 Flyway 条目。

在数据库运行如下 SQL 语句:

delete from flyway_schema_history where version = '1.1';

现在,再次运行 mvn spring-boot:run,就会看到迁移已成功应用。

不过,直接操作数据库可能并不理想,人工操作难免会出纰漏。

5、Flyway Repair

5.1、修复失败的迁移

继续添加另一个损坏的迁移 V1_2__add_table.sql 文件,运行应用,回到迁移失败的状态。

另一种修复数据库状态的方法是使用 flyway:repair 工具。修正 SQL 文件后,可以运行以下命令,而不是手动修改 flyway_schema_history 表:

mvn flyway:repair

结果如下:

Successfully repaired schema history table "PUBLIC"."flyway_schema_history"

在底层,Flyway 只是从 flyway_schema_history 表中删除迁移失败的条目。

现在,再次运行 flyway:info,可以看到上次迁移的状态已从 “Failed” 变为 “Pending”。

再次运行应用。可以看到,现在已经成功应用了修正后的迁移。

5.2、重新调整校验和(Checksum)

通常建议不要更改已成功应用的迁移。但在某些情况下可能无法避免更改迁移。

修改迁移文件 V1_1__add_table.sql,在文件开头添加注释。

现在运行应序,会看到类似 “Migration checksum mismatch” 的错误信息:

Migration checksum mismatch for migration version 1.1
-> Applied to database : 314944264
-> Resolved locally    : 1304013179

出现这种情况是因为我们更改了已应用的迁移,而 Flyway 检测到了不一致。

为了重新调整校验和,可以使用相同的 flyway:repair 命令。不过,这次不会执行迁移。只有 flyway_schema_history 表中版本 1.1 条目的校验和会被更新,以反映更新后的迁移文件。

修复后再次运行应用,会发现应用已成功启动。

注意,在本例中,我们通过 Maven 使用了 flyway:repair。另一种方法是安装 Flyway 命令行工具并运行 flyway repair 命令。效果是一样的:flyway repair 会删除 flyway_schema_history 表中失败的迁移,并重新调整已应用迁移的校验和。

6、Flyway 回调

如果不想手动干预,可以考虑在迁移失败后自动清除 flyway_schema_history 中的失败条目。为此,可以使用 afterMigrateError Flyway Callback(回调)。

首先创建 SQL 回调文件 db/callback/afterMigrateError__repair.sql

DELETE FROM flyway_schema_history WHERE success=false;

只要发生迁移错误,这将自动从 Flyway 状态历史记录中删除任何失败的条目。

创建 application-callbacks.properties 配置文件,在 Flyway locations 列表中包含 db/callback 文件夹:

spring.flyway.locations=classpath:db/migration,classpath:db/callback

现在,添加另一个被破坏的迁移文件 V1_3__add_table.sql。然后,运行应用,包括 callbacks Profile:

mvn spring-boot:run -Dspring-boot.run.profiles=h2,callbacks
...
Migrating schema "PUBLIC" to version 1.3 - add table
Migration of schema "PUBLIC" to version 1.3 - add table failed!
...
Executing SQL callback: afterMigrateError - repair

不出所料,迁移失败了,但 afterMigrateError 回调运行并清理了 flyway_schema_history

只需更正 V1_3__add_table.sql 迁移文件并再次运行应用,就可以应用更正后的迁移。

7、总结

本文介绍了从失败的 Flyway 迁移中恢复的不同方法。像 PostgreSQL 这样的数据库(即支持 DDL 事务的数据库)无需额外工作即可修复 Flyway 数据库状态,其他数据库则需要手动删除 Flyway 历史记录或者是使用 Flyway Repair 工具来自动清理。


Ref:https://www.baeldung.com/spring-boot-flyway-repair