背景
产品迭代使用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 implementsCallback {//Ensures that this callback handles both events
@Overridepublic booleansupports(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
@Overridepublic booleancanHandleInTransaction(Event event, Context context) {return true;
}//Send a notification when either event happens.
@Overridepublic voidhandle(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 implementsFlywayConfiguration {private final Listcallbacks;public voidsetCallbacks(FlywayCallback... callbacks) {this.callbacks.clear();this.callbacks.addAll(Arrays.asList(callbacks));
}
}
有个callbacks的set方法,可以尝试用spring注入的方式配置,实现代码如下:
importlombok.SneakyThrows;importlombok.extern.slf4j.Slf4j;importorg.apache.commons.collections.CollectionUtils;importorg.flywaydb.core.api.MigrationInfo;importorg.flywaydb.core.api.callback.FlywayCallback;importorg.springframework.context.annotation.Configuration;import java.sql.*;importjava.util.ArrayList;importjava.util.List;/*** Flyway迭代升级SQL脚本钩子
* 主要作用:
* 1、初始化VERSION表
* 2、写入当前迭代版本号,根据数据库中是否存在数据表判断*/@Slf4j
@Configurationpublic class InitFlywayCallback implementsFlywayCallback {
@Overridepublic voidbeforeClean(Connection connection) {
}
@Overridepublic voidafterClean(Connection connection) {
}
@Overridepublic voidbeforeMigrate(Connection connection) {
}
@Overridepublic voidafterMigrate(Connection connection) {
}
@Overridepublic voidbeforeUndo(Connection connection) {
}
@Overridepublic voidbeforeEachUndo(Connection connection, MigrationInfo migrationInfo) {
}
@Overridepublic voidafterEachUndo(Connection connection, MigrationInfo migrationInfo) {
}
@Overridepublic voidafterUndo(Connection connection) {
}
@Overridepublic voidbeforeEachMigrate(Connection connection, MigrationInfo migrationInfo) {
}
@Overridepublic voidafterEachMigrate(Connection connection, MigrationInfo migrationInfo) {
}
@SneakyThrows
@Overridepublic voidbeforeValidate(Connection connection) {
log.info("Flyway执行拦截");}
@Overridepublic voidafterValidate(Connection connection) {
}
@Overridepublic voidbeforeBaseline(Connection connection) {
}
@Overridepublic voidafterBaseline(Connection connection) {
}
@Overridepublic voidbeforeRepair(Connection connection) {
}
@Overridepublic voidafterRepair(Connection connection) {
}
@Overridepublic voidbeforeInfo(Connection connection) {
}
@Overridepublic voidafterInfo(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.7by 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事件描述,官网文档:
NameExecution
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
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
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