SpringBoot实现Flyway的Callback回调钩子
背景
产品迭代使用CI/CD升级过程中,需要对不同发布环境的不同产品版本进行数据库迭代升级,我们在中间某次产品迭代时加入了Flyway中间件以实现数据库结构的自动化升级。
需求
由于是迭代过程中加入的Flyway,而不是一开始就使用,所以Flyway的版本表和版本记录数据在已经发布过的环境中是不存在的,而且每个环境的产品版本也不同,数据库结构迭代升级首先需要确定当前产品的版本,再执行相应的升级脚本。所以我们需要在发布环境升级时,在Flyway执行之前先根据数据库现状写入版本表和版本记录数据,才能让Flyway正常执行迭代升级脚本。
Flyway Hooks/Callback
查阅官方文档知道Flyway有个Hooks,官网文档,文档详细描述如下:
Building upon that are the Java-based Callbacks when you need more power or flexibility in a Callback than SQL can offer you.
They can be created by implementing the Callback interface:
public class MyNotifierCallback implements Callback { // Ensures that this callback handles both events @Override public boolean supports(Event event, Context context) { return event.equals(Event.AFTER_MIGRATE) || event.equals(Event.AFTER_MIGRATE_ERROR); } // Not relevant if we don't interact with the database @Override public boolean canHandleInTransaction(Event event, Context context) { return true; } // Send a notification when either event happens. @Override public void handle(Event event, Context context) { String notification = event.equals(Event.AFTER_MIGRATE) ? "Success" : "Failed"; // ... Notification logic ... notificationService.send(notification); } String getCallbackName() { return "MyNotifier"; } }
In order to be picked up by Flyway, Java-based Callbacks must implement the Callback interface. Flyway will automatically scan for and load all callbacks found in the db/callback
package. Additional callback classes or scan locations can be specified by the flyway.callbacks
configuration property.
SpringBoot实现
根据官方文档描述,需要实现Callback并配置flyway.callbacks参数,但是在springboot配置文件中并没有找到关于spring.flyway.callbacks或者flyway.callbacks的配置项
查看了下源码找到了原因,callbacks属性被定义为了final,所以配置文件中不能设置callbacks配置项,关键代码截图如下:
public class Flyway implements FlywayConfiguration { private final List<FlywayCallback> callbacks; public void setCallbacks(FlywayCallback... callbacks) { this.callbacks.clear(); this.callbacks.addAll(Arrays.asList(callbacks)); } }
有个callbacks的set方法,可以尝试用spring注入的方式配置,实现代码如下:
import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; import org.flywaydb.core.api.MigrationInfo; import org.flywaydb.core.api.callback.FlywayCallback; import org.springframework.context.annotation.Configuration; import java.sql.*; import java.util.ArrayList; import java.util.List; /** * Flyway迭代升级SQL脚本钩子 * 主要作用: * 1、初始化VERSION表 * 2、写入当前迭代版本号,根据数据库中是否存在数据表判断 */ @Slf4j @Configuration public class InitFlywayCallback implements FlywayCallback { @Override public void beforeClean(Connection connection) { } @Override public void afterClean(Connection connection) { } @Override public void beforeMigrate(Connection connection) { } @Override public void afterMigrate(Connection connection) { } @Override public void beforeUndo(Connection connection) { } @Override public void beforeEachUndo(Connection connection, MigrationInfo migrationInfo) { } @Override public void afterEachUndo(Connection connection, MigrationInfo migrationInfo) { } @Override public void afterUndo(Connection connection) { } @Override public void beforeEachMigrate(Connection connection, MigrationInfo migrationInfo) { } @Override public void afterEachMigrate(Connection connection, MigrationInfo migrationInfo) { } @SneakyThrows @Override public void beforeValidate(Connection connection) { log.info("Flyway执行拦截"); } @Override public void afterValidate(Connection connection) { } @Override public void beforeBaseline(Connection connection) { } @Override public void afterBaseline(Connection connection) { } @Override public void beforeRepair(Connection connection) { } @Override public void afterRepair(Connection connection) { } @Override public void beforeInfo(Connection connection) { } @Override public void afterInfo(Connection connection) { } }
项目启动打印结果如下:
2020-12-23 09:42:53.025 dassets 13092 [--] [ INFO] [org.flywaydb.core.internal.util.VersionPrinter.info:44] [ main] [Flyway Community Edition 5.0.7 by Boxfuse] 2020-12-23 09:43:03.461 dassets 13092 [--] [ INFO] [org.flywaydb.core.internal.database.DatabaseFactory.info:44] [ main] [Database: jdbc:mysql://10.101.6.105:3306/user (MySQL 5.7)] 2020-12-23 09:43:03.675 dassets 13092 [--] [ INFO] [com.cestc.dassets.interceptor.InitFlywayCallback.beforeValidate:76] [ main] [Flyway执行拦截]
至此,callback执行成功!
最后附一个Flyway的Callback事件描述,官网文档:
Name | Execution |
---|---|
beforeMigrate | Before Migrate runs |
beforeRepeatables | Before all repeatable migrations during Migrate |
beforeEachMigrate | Before every single migration during Migrate |
beforeEachMigrateStatement Flyway Teams | Before every single statement of a migration during Migrate |
afterEachMigrateStatement Flyway Teams | After every single successful statement of a migration during Migrate |
afterEachMigrateStatementError Flyway Teams | After every single failed statement of a migration during Migrate |
afterEachMigrate | After every single successful migration during Migrate |
afterEachMigrateError | After every single failed migration during Migrate |
afterMigrate | After successful Migrate runs |
afterVersioned | After all versioned migrations during Migrate |
afterMigrateError | After failed Migrate runs |
beforeUndo Flyway Teams | Before Undo runs |
beforeEachUndo Flyway Teams | Before every single migration during Undo |
beforeEachUndoStatement Flyway Teams | Before every single statement of a migration during Undo |
afterEachUndoStatement Flyway Teams | After every single successful statement of a migration during Undo |
afterEachUndoStatementError Flyway Teams | After every single failed statement of a migration during Undo |
afterEachUndo Flyway Teams | After every single successful migration during Undo |
afterEachUndoError Flyway Teams | After every single failed migration during Undo |
afterUndo Flyway Teams | After successful Undo runs |
afterUndoError Flyway Teams | After failed Undo runs |
beforeClean | Before Clean runs |
afterClean | After successful Clean runs |
afterCleanError | After failed Clean runs |
beforeInfo | Before Info runs |
afterInfo | After successful Info runs |
afterInfoError | After failed Info runs |
beforeValidate | Before Validate runs |
afterValidate | After successful Validate runs |
afterValidateError | After failed Validate runs |
beforeBaseline | Before Baseline runs |
afterBaseline | After successful Baseline runs |
afterBaselineError | After failed Baseline runs |
beforeRepair | Before Repair runs |
afterRepair | After successful Repair runs |
afterRepairError | After failed Repair runs |