1. 引言
Flyway库通过跟踪以SQL源代码形式存储的变更来实现数据库版本控制。*每个变更集被称为一个迁移(migration)***。
这些迁移会使用一组命令(包括migrate、clean、info、validate、baseline和repair)按顺序应用到数据库。应用过程会根据目标数据库的当前版本进行严格控制。
虽然常规迁移足以覆盖大多数场景,但回调机制在特定情况下能提供更灵活的解决方案。本文将探讨如何利用Flyway回调机制,在其提供的各种命令生命周期中插入自定义逻辑。
2. 典型应用场景
当遇到需要高度灵活性的特殊需求时,回调机制就能派上用场。以下是几个典型场景:
- 重建物化视图 – 当迁移操作影响物化视图的基表时,我们可能需要重建物化视图。SQL回调非常适合执行这类逻辑
- 刷新缓存 – 如果迁移修改了缓存中的数据,我们可以用回调清除缓存,确保应用从数据库获取最新数据
- 调用外部系统 – 通过回调,我们可以调用外部系统(使用任意技术)。例如发布事件、发送邮件或触发服务器重启
3. 支持的回调类型
每个Flyway命令都有对应的before和after回调事件。关于这些命令的详细信息,可参考Flyway核心文章或官方文档。
- BEFORE_ 事件在操作执行前触发
- AFTER_ 事件在操作成功后触发,还包括两个更细粒度的事件:
- ERROR 等价事件在操作失败后触发
- OPERATION_FINISH 事件在操作完成后触发
- *migrate和*undo命令还提供*_EACH*事件**,为每个单独的迁移触发。这两个命令需要额外回调是因为它们通常涉及多个迁移的执行
完整回调事件列表见Event类文档
例如,clean命令的回调事件是BEFORE_CLEAN和AFTER_CLEAN,分别在命令执行前后立即触发。回顾引言部分,可用命令包括:migrate、clean、info、validate、baseline和repair。
Flyway提供这些额外钩子,让我们能在最细粒度(即单个迁移)上控制自定义回调逻辑。
4. 依赖配置
通过实际示例了解回调机制。首先在pom.xml中声明flyway-core依赖:
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>9.16.3</version>
</dependency>
最新版本可在Maven中央仓库获取。
5. 回调实现方式
Flyway支持两种回调实现:Java和SQL。前者灵活性最高,可执行任意代码;后者则直接与数据库交互。
5.1. Java回调
Java API契约由Callback接口定义。最简单的自定义回调实现方式如下:
public class ExampleFlywayCallback implements Callback {
private final Log log = LogFactory.getLog(getClass());
@Override
public boolean supports(Event event, Context context) {
return event == Event.AFTER_EACH_MIGRATE;
}
@Override
public boolean canHandleInTransaction(Event event, Context context) {
return true;
}
@Override
public void handle(Event event, Context context) {
if (event == Event.AFTER_EACH_MIGRATE) {
log.info("> afterEachMigrate");
}
}
@Override
public String getCallbackName() {
return ExampleFlywayCallback.class.getSimpleName();
}
}
5.2. SQL回调
SQL回调通过特定命名文件定义,这些文件需存放在配置为*locations(s)*的目录中。Flyway会在配置位置查找SQL回调文件并执行。
例如,在配置为location的目录中创建beforeEachMigrate.sql文件,该脚本会在migrate命令执行期间、每个迁移脚本运行前执行。
6. 配置与执行
以下示例同时配置了Java回调和两个SQL脚本位置(一个存放迁移脚本,一个存放SQL回调):
@Test
public void migrateWithSqlAndJavaCallbacks() {
Flyway flyway = Flyway.configure()
.dataSource(dataSource)
.locations("db/migration", "db/callbacks")
.callbacks(new ExampleFlywayCallback())
.load();
flyway.migrate();
}
⚠️ 注意:虽然迁移和SQL回调可以放在同一位置,但本示例分开存放以演示隔离管理。
当Java和SQL同时定义beforeEachMigrate回调时:
- Java回调优先执行
- 紧接着执行SQL回调
测试输出清晰展示了执行顺序:
21:50:45.677 [main] INFO c.b.f.FlywayApplicationUnitTest - > migrateWithSqlAndJavaCallbacks
21:50:45.848 [main] INFO o.f.c.i.license.VersionPrinter - Flyway Community Edition 8.0.0 by Redgate
21:50:45.849 [main] INFO o.f.c.i.d.base.BaseDatabaseType - Database: jdbc:h2:mem:DATABASE (H2 1.4)
21:50:45.938 [main] INFO o.f.core.internal.command.DbValidate - Successfully validated 2 migrations (execution time 00:00.021s)
21:50:45.951 [main] INFO o.f.c.i.s.JdbcTableSchemaHistory - Creating Schema History table "PUBLIC"."flyway_schema_history" ...
21:50:46.003 [main] INFO o.f.c.i.c.SqlScriptCallbackFactory - Executing SQL callback: beforeMigrate -
21:50:46.015 [main] INFO o.f.core.internal.command.DbMigrate - Current version of schema "PUBLIC": << Empty Schema >>
21:50:46.023 [main] INFO o.f.c.i.c.SqlScriptCallbackFactory - Executing SQL callback: beforeEachMigrate -
21:50:46.024 [main] INFO o.f.core.internal.command.DbMigrate - Migrating schema "PUBLIC" to version "1.0 - add table one"
21:50:46.025 [main] INFO c.b.f.ExampleFlywayCallback - > afterEachMigrate
21:50:46.046 [main] INFO o.f.c.i.c.SqlScriptCallbackFactory - Executing SQL callback: beforeEachMigrate -
21:50:46.046 [main] INFO o.f.core.internal.command.DbMigrate - Migrating schema "PUBLIC" to version "1.1 - add table two"
21:50:46.047 [main] INFO c.b.f.ExampleFlywayCallback - > afterEachMigrate
21:50:46.067 [main] INFO o.f.core.internal.command.DbMigrate - Successfully applied 2 migrations to schema "PUBLIC", now at version v1.1 (execution time 00:00.060s)
7. 总结
本文详细介绍了Flyway回调机制在Java和SQL中的实现方式,分析了典型应用场景并通过示例演示了具体用法。掌握回调机制能帮我们解决迁移过程中的特殊需求,避免踩坑。
完整源代码可在GitHub仓库获取。