文章目录
Flyway版本信息
flywayVersion = “5.2.4”
迁移状态枚举
// 需要注意,每个枚举内部定义好了变量,用于判定是否在类路径中,是否应用过,是否应用失败。
MigrationState(String displayName, boolean resolved, boolean applied, boolean failed) {
this.displayName = displayName;
this.resolved = resolved;
this.applied = applied;
this.failed = failed;
}
public enum MigrationState {
// 类路径中存在的至今未被应用的
PENDING("Pending", true, false, false),
// 类路径中存在的至今未被应用的,但是高于target版本。target是迁移时指定的最高版本
ABOVE_TARGET("Above Target", true, false, false),
// 类路径中存在的低于baseline而未被应用
BELOW_BASELINE("Below Baseline", true, false, false),
// baseline本身,这种状态的认为是类路径和db都存在且成功应用
BASELINE("Baseline", true, true, false),
// 类路径中存在。但是因为高于此版本的迁移被应用了,导致此版本迁移被忽略,没有应用。
// 插队脚本造成(主客观),可以通过提高脚本版本或者允许outOfOrder解决。清除历史重新迁移也可以。
IGNORED("Ignored", true, false, false),
// 类路径中不存在,但DB中应用成功。
// 可能是将多个旧的迁移集合到一个迁移文件中造成,也可能就是从类路径中删除了。
MISSING_SUCCESS("Missing", false, true, false),
// 类路径中不存在,但DB中也应用失败。
// 类路径中某迁移失败后,又删除脚本可造成。
MISSING_FAILED("Failed (Missing)", false, true, true),
// 正常成功的迁移。
SUCCESS("Success", true, true, false),
// 迁移成功了,但是还没完成(???看起来是个中间状态)
UNDONE("Undone", true, true, false),
/**
* This undo migration is ready to be applied if desired.
*/
AVAILABLE("Available", true, false, false),
// 失败的迁移。
FAILED("Failed", true, true, true),
// 成功的迁移。但是没有按照给定版本的严格顺序。如果重新执行所有的迁移,(由于脚本迁移顺序)可能会有不同的结果。
OUT_OF_ORDER("Out of Order", true, true, false),
// 类路径中不存在,但是DB已应用成功且版本高于类路径中最高版本。
FUTURE_SUCCESS("Future", false, true, false),
// 相对于FUTURE_SUCCESS,只是DB中失败了。
FUTURE_FAILED("Failed (Future)", false, true, true),
// R类型迁移,且类路径中的已经发生了变更,应该需要被重新应用
OUTDATED("Outdated", true, true, false),
// 这是一个过时的可重复迁移,并且已经被更新的运行所取代。即,同样一个R类型的迁移,经过修改后运行成功了,那么老的一条记录的就会显示为此状态。
SUPERSEDED("Superseded", true, true, false);
private final String displayName;
private final boolean resolved;
private final boolean applied;
private final boolean failed;
MigrationState(String displayName, boolean resolved, boolean applied, boolean failed) {
this.displayName = displayName;
this.resolved = resolved;
this.applied = applied;
this.failed = failed;
}
}
验证与迁移
FlywayMigrationInitializer实现了InitializingBean,在afterPropertiesSet方法中执行了校验、迁移等动作。
public class FlywayMigrationInitializer implements InitializingBean, Ordered
启动入口
public FlywayMigrationInitializer(Flyway flyway) {
this(flyway, null);
}
public FlywayMigrationInitializer(Flyway flyway, FlywayMigrationStrategy migrationStrategy) {
Assert.notNull(flyway, "Flyway must not be null");
this.flyway = flyway;
this.migrationStrategy = migrationStrategy;
}
// 构造函数的migrationStrategy是可选的,如果存在,使用指定的策略进行迁移
@Override
public void afterPropertiesSet() throws Exception {
if (this.migrationStrategy != null) {
this.migrationStrategy.migrate(this.flyway);
}
else {
this.flyway.migrate();
}
}
migrate方法中,通过execute方法调用Command的匿名实现类。
execute调用Command前做了一些数据准备工作,如创建DB连接,拿到history信息,扫描迁移文件。
主体流程
public int migrate() throws FlywayException {
return execute(new Command<Integer>() {
public Integer execute(MigrationResolver migrationResolver,
SchemaHistory schemaHistory,
Database database,
Schema[] schemas,
CallbackExecutor callbackExecutor) {
// validateOnMigrate默认为true,即迁移前总是校验
if (configuration.isValidateOnMigrate()) {
// 参考验证流程
doValidate(database, migrationResolver, schemaHistory, schemas, callbackExecutor,
true // Always ignore pending migrations when validating before migrating
);
}
// 如果指定的schemas不存在,则创建
new DbSchemas(database, schemas, schemaHistory).create();
if (!schemaHistory.exists()) {
List<Schema> nonEmptySchemas = new ArrayList<>();
for (Schema schema : schemas) {
if (!schema.empty()) {
nonEmptySchemas.add(schema);
}
}
if (!nonEmptySchemas.isEmpty()) {
if (configuration.isBaselineOnMigrate()) {
// TODO baseline用途
doBaseline(schemaHistory, database, schemas, callbackExecutor);
} else {
// Second check for MySQL which is sometimes flaky otherwise
if (!schemaHistory.exists()) {
throw new FlywayException("Found non-empty schema(s) "
+ StringUtils.collectionToCommaDelimitedString(nonEmptySchemas)
+ " without schema history table! Use baseline()"
+ " or set baselineOnMigrate to true to initialize the schema history table.");
}
}
}
}
// 选中了第一个schemas进行迁移。参考迁移流程
return new DbMigrate(database, schemaHistory, schemas[0], migrationResolver, configuration,
callbackExecutor).migrate();
}
}, true);
}
验证:validate
private void doValidate(Database database, MigrationResolver migrationResolver, SchemaHistory schemaHistory,
Schema[] schemas, CallbackExecutor callbackExecutor, boolean ignorePending) {
String validationError =
new DbValidate(database, schemaHistory, schemas[0], migrationResolver,
// 验证逻辑,返回值是错误信息,当错误时,后续会抛异常或走清理逻辑。
configuration, ignorePending, callbackExecutor).validate();
if (validationError != null) {
// 一般,生产环境严禁配置这里生效,会造成生产数据丢失。
if (configuration.isCleanOnValidationError()) {
doClean(database, schemaHistory, schemas, callbackExecutor);
} else {
throw new FlywayException("Validate failed: " + validationError);
}
}
}
批量校验主体流程
public String validate() {
// 如果flyway的历史表不存在,将不会进行具体的验证逻辑。但会尝试通过迁移解析器migrationResolver加载当前配置的迁移脚本,以进行后续的迁移工作。
if (!schema.exists()) {
if (!migrationResolver.resolveMigrations(new Context() {
// Context中匿名实现即时返回了当前的配置。
@Override
public Configuration getConfiguration() {
return configuration;
}
// 如果没有加载到任何迁移脚本,且不允许pending,将会返回错误信息
}).isEmpty() && !pending) {
return "Schema " + schema + " doesn't exist yet";
}
// 如果允许pending,则不进行抛错,将直接通过验证环节;或者确实找到了需要迁移的脚本。
return null;
}
callbackExecutor.onEvent(Event.BEFORE_VALIDATE);
LOG.debug("Validating migrations ...");
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Pair<Integer, String> result = new TransactionTemplate(connection.getJdbcConnection()).execute(new Callable<Pair<Integer, String>>() {
@Override
public Pair<Integer, String> call() {
MigrationInfoServiceImpl migrationInfoService =
new MigrationInfoServiceImpl(migrationResolver, schemaHistory, configuration,
// flyway将会更新的最新版本,默认为最新版本。 configuration的默认实现参考ClassicConfiguration
configuration.getTarget(),
configuration.isOutOfOrder(),
pending,
configuration.isIgnoreMissingMigrations(),
configuration.isIgnoreIgnoredMigrations(),
configuration.isIgnoreFutureMigrations());
// refresh方法刷新了(重新加载了)db和classpath下的迁移信息。db中对应的是已经应用的迁移,classpath下是全量的,由双方对比,得出以应用的最新版本,记录在(context.lastApplied)
migrationInfoService.refresh();
int count = migrationInfoService.all().length;
// 内部循环验证各个脚本情况,当出现错误时立即返回,否则一直遍历到最后,返回null。
String validationError = migrationInfoService.validate();
return Pair.of(count, validationError);
}
});
stopWatch.stop();
String error = result.getRight();
if (error == null) {
int count = result.getLeft();
if (count == 1) {
LOG.info(String.format("Successfully validated 1 migration (execution time %s)",
TimeFormat.format(stopWatch.getTotalTimeMillis())));
} else {
LOG.info(String.format("Successfully validated %d migrations (execution time %s)",
count, TimeFormat.format(stopWatch.getTotalTimeMillis())));
}
callbackExecutor.onEvent(Event.AFTER_VALIDATE);
} else {
callbackExecutor.onEvent(Event.AFTER_VALIDATE_ERROR);
}
return error;
}
// migrationInfoService.validate()
public String validate() {
// 循环验证,当出现错误时立即返回,否则一直遍历到最后,返回null。
for (MigrationInfoImpl migrationInfo : migrationInfos) {
String message = migrationInfo.validate();
if (message != null) {
return message;
}
}
return null;
}
校验
migrationInfo.validate()
public String validate() {
// 详见获取状态
MigrationState state = getState();
// 超过了target,直接通过
if (MigrationState.ABOVE_TARGET.equals(state)) {
return null;
}
// failed状态只是来源于db中的记录。
// 如果当前迁移在db中标记失败,且(不允许未来的迁移或状态不是FUTURE_FAILED),那么将抛出异常。
// 也就是说,如果DB中最新的一条迁移记录是失败的,那么只要是执行了验证逻辑,总是要报错的。
if (state.isFailed() && (!context.future || MigrationState.FUTURE_FAILED != state)) {
if (getVersion() == null) {
return "Detected failed repeatable migration: " + getDescription();
}
return "Detected failed migration to version " + getVersion() + " (" + getDescription() + ")";
}
// 类路径中信息没有时,对于V类型迁移来说,miss和future两种情况,依配置而定是否抛错。不允许miss和future会直接抛错。
// 如果允许miss,那么仍然需要满足属于是miss类的情况。
// 如果允许future,那么需要满足属于是future的情况,无论成功失败。
if ((resolvedMigration == null)
&& (appliedMigration.getType() != MigrationType.SCHEMA)
&& (appliedMigration.getType() != MigrationType.BASELINE)
&& (appliedMigration.getVersion() != null)
&& (!context.missing || (MigrationState.MISSING_SUCCESS != state && MigrationState.MISSING_FAILED != state))
&& (!context.future || (MigrationState.FUTURE_SUCCESS != state && MigrationState.FUTURE_FAILED != state))) {
return "Detected applied migration not resolved locally: " + getVersion();
}
// 这个校验可能就分纯校验和校验后立即迁移的情况了。
// pending本身是需要迁移的脚本状态,如果校验时配置了不允许pending,那么这里一旦出现pending,就会抛错。
// ignored的逻辑也一样。
if (!context.pending && MigrationState.PENDING == state || (!context.ignored && MigrationState.IGNORED == state)) {
if (getVersion() != null) {
return "Detected resolved migration not applied to database: " + getVersion();
}
return "Detected resolved repeatable migration not applied to database: " + getDescription();
}
// 如果不允许pending(实际上这种配置,目的是要求不要有任何新的迁移),但是存在需要迁移的R类型脚本,仍提示错误。
if (!context.pending && MigrationState.OUTDATED == state) {
return "Detected outdated resolved repeatable migration that should be re-applied to database: " + getDescription();
}
// db、类路径都存在
if (resolvedMigration != null && appliedMigration != null) {
// V与R的标识不同。
String migrationIdentifier = appliedMigration.getVersion() == null ?
// Repeatable migrations
appliedMigration.getScript() :
// Versioned migrations
"version " + appliedMigration.getVersion();
// 如果R类型脚本,或者版本高于baseline
// 需要注意 migrationInfo的getVersion()方法是先去拿了db中的版本,没有的话,再去拿类路径下的版本。依然可以说明为null时,就是R类型
if (getVersion() == null || getVersion().compareTo(context.baseline) > 0) {
// 纯粹的脚本类型判断。db与类路径需要保持一致
if (resolvedMigration.getType() != appliedMigration.getType()) {
return createMismatchMessage("type", migrationIdentifier,
appliedMigration.getType(), resolvedMigration.getType());
}
// V类型,或者(允许pending时R类型不需要pending且不是已经被应用过的状态)时,检查校验和是否一样。
// 关于SUPERSEDED的含义,需要结合getState和context.latestRepeatableRuns维护逻辑去判断。
if (resolvedMigration.getVersion() != null
|| (context.pending && MigrationState.OUTDATED != state && MigrationState.SUPERSEDED != state)) {
// 检查校验和
// 校验和的实现org.flywaydb.core.internal.resource.AbstractLoadableResource,一次读一行,忽略换行符。尾行的换行符不会影响校验和。但可能影响git
if (!ObjectUtils.nullSafeEquals(resolvedMigration.getChecksum(), appliedMigration.getChecksum())) {
return createMismatchMessage("checksum", migrationIdentifier,
appliedMigration.getChecksum(), resolvedMigration.getChecksum());
}
}
// 校验描述
if (!AbbreviationUtils.abbreviateDescription(resolvedMigration.getDescription())
.equals(appliedMigration.getDescription())) {
return createMismatchMessage("description", migrationIdentifier,
appliedMigration.getDescription(), resolvedMigration.getDescription());
}
}
}
return null;
}
获取状态
org.flywaydb.core.internal.info.MigrationInfoImpl#getState
所有的状态信息都是相对于类路径下的迁移来说的,
比如future,含义是db中的版本高于了类路径下的版本
missing,含义是db中有但是类路径中没有的
ignored,含义是db中没有,但是类路径中有,而且版本非最高的。
pengding,含义是db中没有,类路径中有且版本高于db中的。
@Override
public MigrationState getState() {
// 如果db中没有这个迁移的信息
if (appliedMigration == null) {
// V类型
if (resolvedMigration.getVersion() != null) {
// 低于基准状态
if (resolvedMigration.getVersion().compareTo(context.baseline) < 0) {
return MigrationState.BELOW_BASELINE;
}
// 设定了迁移最高版本,且超过其版本
if (context.target != null && resolvedMigration.getVersion().compareTo(context.target) > 0) {
return MigrationState.ABOVE_TARGET;
}
// 这里体现出了outOfOrder配置项的用途。即,如果db中没有这个迁移的信息,并且比db中最新版本要低,说明是个插队的。
// 此时,如果配置成了false,那么将返回忽略;如果是true,那么将跳出这个if判断,返回PENDING,后续会得到应用。
if ((resolvedMigration.getVersion().compareTo(context.lastApplied) < 0) && !context.outOfOrder) {
return MigrationState.IGNORED;
}
}
return MigrationState.PENDING;
}
// baseline
if (MigrationType.BASELINE == appliedMigration.getType()) {
return MigrationState.BASELINE;
}
// DB中有,类路径没有
if (resolvedMigration == null) {
// SCHEMA类型
if (MigrationType.SCHEMA == appliedMigration.getType()) {
return MigrationState.SUCCESS;
}
// 如果是个R,或者版本比类路径的小,说明错过了,根据DB中的状态,返回是否成功
if ((appliedMigration.getVersion() == null) || getVersion().compareTo(context.lastResolved) < 0) {
if (appliedMigration.isSuccess()) {
return MigrationState.MISSING_SUCCESS;
}
return MigrationState.MISSING_FAILED;
} else {
// 不是R,且高于类路径版本,时间上相对于类路径属于未来,根据DB状态,返回是否成功。
if (appliedMigration.isSuccess()) {
return MigrationState.FUTURE_SUCCESS;
}
return MigrationState.FUTURE_FAILED;
}
}
// 走到这里,说明DB和类路径都存在,如果DB中标记为失败状态就返回失败
if (!appliedMigration.isSuccess()) {
return MigrationState.FAILED;
}
// 后续说明在DB里成功了
// 如果是个R,根据是否变更决定是否需要执行
if (appliedMigration.getVersion() == null) {
if (appliedMigration.getInstalledRank() == context.latestRepeatableRuns.get(appliedMigration.getDescription())) {
// 内容没有变化,代表成功
if (ObjectUtils.nullSafeEquals(appliedMigration.getChecksum(), resolvedMigration.getChecksum())) {
return MigrationState.SUCCESS;
}
// 内容变化了,需要再次应用
return MigrationState.OUTDATED;
}
// 与latestRepeatableRuns中的rank不对应(其实是低于),说明同个脚本变更了,且已经在db里是成功了。
// 参考R类型状态变更 https://flywaydb.org/getstarted/repeatable
return MigrationState.SUPERSEDED;
}
// 如果是个V
// 对于DB和类路径都有的,且db中成功的,配置了outOfOrder,会影响到最终返回的状态。但是迁移时,总是选择pending,这个选项在这个环节不影响实际已经迁移的内容
if (outOfOrder) {
return MigrationState.OUT_OF_ORDER;
}
return MigrationState.SUCCESS;
}
迁移:migrate
- 如果迁移历史表不存在,就创建。
- 根据configuration.isGroup()属性,将迁移打包到一个事务内进行迁移。由于mysql不支持ddl事务,一般不使用这种方式。
- 迁移前后会有基于迁移前后的事件处理。
public int migrate() throws FlywayException {
// 迁移前事件
callbackExecutor.onMigrateOrUndoEvent(Event.BEFORE_MIGRATE);
int count;
try {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 内部逻辑,如果不存在,就创建
schemaHistory.create();
// 注意这个三元。如果开启组事务,就尝试走组事务。
count = configuration.isGroup() ?
// When group is active, start the transaction boundary early to
// ensure that all changes to the schema history table are either committed or rolled back atomically.
// lock将会开启事务,并通过get_lock函数上一把关于history表的排他锁,但是mysql存在ddl隐式提交(不支持ddl事务),所以这里并不保证组内迁移失败时可以正常回滚。
schemaHistory.lock(new Callable<Integer>() {
@Override
public Integer call() {
return migrateAll();
}
}) :
// For all regular cases, proceed with the migration as usual.
// 内部为每个迁移脚本开启事务。
migrateAll();
stopWatch.stop();
logSummary(count, stopWatch.getTotalTimeMillis());
} catch (FlywayException e) {
callbackExecutor.onMigrateOrUndoEvent(Event.AFTER_MIGRATE_ERROR);
throw e;
}
callbackExecutor.onMigrateOrUndoEvent(Event.AFTER_MIGRATE);
return count;
}
迁移中的事务
schemaHistory.lock方法的实现里开启了事务。
在org.flywaydb.core.internal.command.DbMigrate#migrate方法中,判断如果是configuration.isGroup(),那么将在外部开启事务以执行整个迁移。
如果configuration.isGroup() == false,那么就在migrateAll()方法内部每个循环里为每个迁移脚本开启事务。
批量迁移
private int migrateAll() {
int total = 0;
while (true) {
final boolean firstRun = total == 0;
int count = configuration.isGroup()
// With group active a lock on the schema history table has already been acquired.
? migrateGroup(firstRun)
// Otherwise acquire the lock now. The lock will be released at the end of each migration.
// LOCK方法会根据history表的hashcode,使用mysql的get_lock函数上排它锁,并非锁表(not lock table)
: schemaHistory.lock(new Callable<Integer>() {
@Override
public Integer call() {
return migrateGroup(firstRun);
}
});
total += count;
if (count == 0) {
// No further migrations available
break;
}
}
return total;
}
组迁移
// firstRun标识是否是此次整体迁移中的第一次迁移(一个迁移脚本对应一次迁移),这里只是用于打印了日志。
private Integer migrateGroup(boolean firstRun) {
MigrationInfoServiceImpl infoService =
new MigrationInfoServiceImpl(migrationResolver, schemaHistory, configuration,
configuration.getTarget(), configuration.isOutOfOrder(),
true, true, true, true);
// 迁移前,调用了刷新方法,保证拿到了最新的迁移信息。
// 在校验方法中也调用刷新,因为两者可以独立执行,所以这里是各自为各自的逻辑刷新数据。
infoService.refresh();
// 拿到当前已迁移的最新脚本,参考current方法解读(DB中最新版本)
MigrationInfo current = infoService.current();
MigrationVersion currentSchemaVersion = current == null ? MigrationVersion.EMPTY : current.getVersion();
if (firstRun) {
LOG.info("Current version of schema " + schema + ": " + currentSchemaVersion);
if (configuration.isOutOfOrder()) {
LOG.warn("outOfOrder mode is active. Migration of schema " + schema + " may not be reproducible.");
}
}
// 拿到future类别的,包含失败的和成功的
MigrationInfo[] future = infoService.future();
if (future.length > 0) {
List<MigrationInfo> resolved = Arrays.asList(infoService.resolved());
Collections.reverse(resolved);
if (resolved.isEmpty()) {
LOG.warn("Schema " + schema + " has version " + currentSchemaVersion
+ ", but no migration could be resolved in the configured locations !");
} else {
for (MigrationInfo migrationInfo : resolved) {
// Only consider versioned migrations
if (migrationInfo.getVersion() != null) {
LOG.warn("Schema " + schema + " has a version (" + currentSchemaVersion
+ ") that is newer than the latest available migration ("
+ migrationInfo.getVersion() + ") !");
break;
}
}
}
}
// 拿到标注为失败的
MigrationInfo[] failed = infoService.failed();
if (failed.length > 0) {
// 如果只有一个,且还是future类型的,如果配置了忽略isIgnoreFutureMigrations,那么仅仅打印一下,否则抛异常提示。
if ((failed.length == 1)
&& (failed[0].getState() == MigrationState.FUTURE_FAILED)
&& configuration.isIgnoreFutureMigrations()) {
LOG.warn("Schema " + schema + " contains a failed future migration to version " + failed[0].getVersion() + " !");
} else {
if (failed[0].getVersion() == null) {
throw new FlywayException("Schema " + schema + " contains a failed repeatable migration (" + failed[0].getDescription() + ") !");
}
throw new FlywayException("Schema " + schema + " contains a failed migration to version " + failed[0].getVersion() + " !");
}
}
LinkedHashMap<MigrationInfoImpl, Boolean> group = new LinkedHashMap<>();
// 拿到PENDING状态的,注意,这里是个遍历。内部由configuration.isGroup()去控制是否遍历全部,还是单个就停止
// 根据是否插队,使用group的value进行标记。
// 如果V类型的版本低于current的版本(db中已应用的最新版本),说明是个插队的(新加的,还处于某个版本间的,非最新的)
for (MigrationInfoImpl pendingMigration : infoService.pending()) {
boolean isOutOfOrder = pendingMigration.getVersion() != null
&& pendingMigration.getVersion().compareTo(currentSchemaVersion) < 0;
group.put(pendingMigration, isOutOfOrder);
// 如果没有开启组,那么一次处理一个迁移
if (!configuration.isGroup()) {
// Only include one pending migration if group is disabled
break;
}
}
if (!group.isEmpty()) {
// 真正的迁移入口
applyMigrations(group);
}
return group.size();
}
应用组迁移
private void applyMigrations(final LinkedHashMap<MigrationInfoImpl, Boolean> group) {
// 这里对是否在事务里执行做了一个判断。
// 根据迁移脚本里sql的类型,如DDL,DML进行判断。判断里变量的赋值在org.flywaydb.core.internal.sqlscript.SqlScript#addStatement
// 深究的话,这里在构建SqlScript的时候,分别对迁移做了事务相关的判断,如果mixed模式未开启,且ddl和dml同时存在于一个sql脚本中,flyway会报错提示不支持。参考 关于脚本事务
boolean executeGroupInTransaction = isExecuteGroupInTransaction(group);
final StopWatch stopWatch = new StopWatch();
try {
if (executeGroupInTransaction) {
new TransactionTemplate(connectionUserObjects.getJdbcConnection()).execute(new Callable<Object>() {
@Override
public Object call() {
doMigrateGroup(group, stopWatch);
return null;
}
});
} else {
// 开始迁移
doMigrateGroup(group, stopWatch);
}
} catch (FlywayMigrateException e) {
MigrationInfoImpl migration = e.getMigration();
String failedMsg = "Migration of " + toMigrationText(migration, e.isOutOfOrder()) + " failed!";
if (database.supportsDdlTransactions() && executeGroupInTransaction) {
LOG.error(failedMsg + " Changes successfully rolled back.");
} else {
LOG.error(failedMsg + " Please restore backups and roll back database and code!");
stopWatch.stop();
int executionTime = (int) stopWatch.getTotalTimeMillis();
// 迁移失败逻辑
schemaHistory.addAppliedMigration(migration.getVersion(), migration.getDescription(),
migration.getType(), migration.getScript(), migration.getResolvedMigration().getChecksum(), executionTime, false);
}
throw e;
}
}
具体的迁移逻辑
private void doMigrateGroup(LinkedHashMap<MigrationInfoImpl, Boolean> group, StopWatch stopWatch) {
Context context = new Context() {
@Override
public Configuration getConfiguration() {
return configuration;
}
@Override
public java.sql.Connection getConnection() {
return connectionUserObjects.getJdbcConnection();
}
};
// 迁移
// 对于configuration.isGroup()==false 来说,实际上只有一个迁移在group内
// entry的value是outOfOrder,值为true表明是个插队迁移
for (Map.Entry<MigrationInfoImpl, Boolean> entry : group.entrySet()) {
final MigrationInfoImpl migration = entry.getKey();
boolean isOutOfOrder = entry.getValue();
final String migrationText = toMigrationText(migration, isOutOfOrder);
stopWatch.start();
LOG.info("Migrating " + migrationText);
connectionUserObjects.restoreOriginalState();
connectionUserObjects.changeCurrentSchemaTo(schema);
try {
callbackExecutor.setMigrationInfo(migration);
callbackExecutor.onEachMigrateOrUndoEvent(Event.BEFORE_EACH_MIGRATE);
try {
migration.getResolvedMigration().getExecutor().execute(context);
} catch (FlywayException e) {
callbackExecutor.onEachMigrateOrUndoEvent(Event.AFTER_EACH_MIGRATE_ERROR);
throw new FlywayMigrateException(migration, isOutOfOrder, e);
} catch (SQLException e) {
callbackExecutor.onEachMigrateOrUndoEvent(Event.AFTER_EACH_MIGRATE_ERROR);
throw new FlywayMigrateException(migration, isOutOfOrder, e);
}
LOG.debug("Successfully completed migration of " + migrationText);
callbackExecutor.onEachMigrateOrUndoEvent(Event.AFTER_EACH_MIGRATE);
} finally {
callbackExecutor.setMigrationInfo(null);
}
stopWatch.stop();
int executionTime = (int) stopWatch.getTotalTimeMillis();
schemaHistory.addAppliedMigration(migration.getVersion(), migration.getDescription(), migration.getType(),
migration.getScript(), migration.getResolvedMigration().getChecksum(), executionTime, true);
}
}
关于脚本事务
if (!mixed && transactionalStatementFound && nonTransactionalStatementFound) {
throw new FlywayException(
"Detected both transactional and non-transactional statements within the same migration"
+ " (even though mixed is false). Offending statement found at line "
+ sqlStatement.getLineNumber() + ": " + sqlStatement.getSql()
+ (sqlStatementBuilder.executeInTransaction() ? "" : " [non-transactional]"));
}
private boolean isExecuteGroupInTransaction(LinkedHashMap<MigrationInfoImpl, Boolean> group) {
boolean executeGroupInTransaction = true;
boolean first = true;
for (Map.Entry<MigrationInfoImpl, Boolean> entry : group.entrySet()) {
ResolvedMigration resolvedMigration = entry.getKey().getResolvedMigration();
boolean inTransaction = resolvedMigration.getExecutor().canExecuteInTransaction();
if (first) {
executeGroupInTransaction = inTransaction;
first = false;
continue;
}
if (!configuration.isMixed() && executeGroupInTransaction != inTransaction) {
throw new FlywayException(
"Detected both transactional and non-transactional migrations within the same migration group"
+ " (even though mixed is false). First offending migration:"
+ (resolvedMigration.getVersion() == null ? "" : " " + resolvedMigration.getVersion())
+ (StringUtils.hasLength(resolvedMigration.getDescription()) ? " " + resolvedMigration.getDescription() : "")
+ (inTransaction ? "" : " [non-transactional]"));
}
executeGroupInTransaction &= inTransaction;
}
return executeGroupInTransaction;
}
修复:repair
org.flywaydb.core.internal.command.DbRepair#repair
用途
删除失败的迁移条目
将db中已应用的校验和、描述和类型更新为类路径中可用迁移的校验和、描述和类型
流程
- 删除失败的记录
- 刷新上下文与迁移信息
- 进行修复
public void repair() {
callbackExecutor.onEvent(Event.BEFORE_REPAIR);
try {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
boolean repaired = new TransactionTemplate(connection.getJdbcConnection()).execute(new Callable<Boolean>() {
public Boolean call() {
// 1
schemaHistory.removeFailedMigrations();
// 2
migrationInfoService.refresh();
// 3 修复逻辑
return alignAppliedMigrationsWithResolvedMigrations();
}
});
stopWatch.stop();
LOG.info("Successfully repaired schema history table " + schemaHistory + " (execution time "
+ TimeFormat.format(stopWatch.getTotalTimeMillis()) + ").");
// 如果有所修复,并且当前db不支持ddl事务,提示用户需要考虑负面影响。
if (repaired && !database.supportsDdlTransactions()) {
LOG.info("Manual cleanup of the remaining effects the failed migration may still be required.");
}
} catch (FlywayException e) {
callbackExecutor.onEvent(Event.AFTER_REPAIR_ERROR);
throw e;
}
callbackExecutor.onEvent(Event.AFTER_REPAIR);
}
private boolean alignAppliedMigrationsWithResolvedMigrations() {
boolean repaired = false;
// 拿到所有迁移信息
for (MigrationInfo migrationInfo : migrationInfoService.all()) {
MigrationInfoImpl migrationInfoImpl = (MigrationInfoImpl) migrationInfo;
ResolvedMigration resolved = migrationInfoImpl.getResolvedMigration();
AppliedMigration applied = migrationInfoImpl.getAppliedMigration();
// 修复目标是V类型,用户向迁移,且updateNeeded
if (resolved != null
&& resolved.getVersion() != null
&& applied != null
&& !applied.getType().isSynthetic()
// (校验和改变的 或 描述改变的 或 迁移类型改变的 )才需要修复。
&& updateNeeded(resolved, applied)) {
// 将db中的信息修正为类路径中指定的,修正信息包含 校验和、类型、描述。
schemaHistory.update(applied, resolved);
repaired = true;
}
}
return repaired;
}
// 需要更新的,将类路径下和db中信息进行对比
private boolean updateNeeded(ResolvedMigration resolved, AppliedMigration applied) {
// 校验和改变的 或 描述改变的 或 迁移类型改变的
return checksumUpdateNeeded(resolved, applied)
|| descriptionUpdateNeeded(resolved, applied)
|| typeUpdateNeeded(resolved, applied);
}
private boolean checksumUpdateNeeded(ResolvedMigration resolved, AppliedMigration applied) {
return !ObjectUtils.nullSafeEquals(resolved.getChecksum(), applied.getChecksum());
}
private boolean descriptionUpdateNeeded(ResolvedMigration resolved, AppliedMigration applied) {
return !ObjectUtils.nullSafeEquals(resolved.getDescription(), applied.getDescription());
}
private boolean typeUpdateNeeded(ResolvedMigration resolved, AppliedMigration applied) {
return !ObjectUtils.nullSafeEquals(resolved.getType(), applied.getType());
}
通用的重要方法
刷新
org.flywaydb.core.internal.info.MigrationInfoServiceImpl#refresh
通过查找、对比类路径和db下的迁移信息,维护几个重要变量
- context.lastResolved,MigrationVersion类型,类路径下最新的版本(V)
- context.lastApplied,MigrationVersion类型,DB最新应用的V类型版本
- context.baseline,MigrationVersion类型,迁移基准
- context.latestRepeatableRuns,db中R类型最新的rank(类比lastApplied)
- MigrationInfoServiceImpl.migrationInfos,List类型,用于后续迁移使用,包含了V和R类型迁移,V按照rank和版本、状态排序,R按照rank、状态和描述排在V后面
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TuWuGy9m-1594284993650)(http://images.mydev.xyz/md/image-20200707174227365.png)]
public void refresh() {
// 从classpath解析获得的迁移信息。包含版本号、描述、校验和等信息,并根据版本排了序。V排在前,R排在后。(实际R的version对应为null,compare写死了null返回1)。
// 解析逻辑参考 org.flywaydb.core.internal.resolver.MigrationInfoHelper#extractVersionAndDescription方法
Collection<ResolvedMigration> resolvedMigrations = migrationResolver.resolveMigrations(context);
// db中已经应用的迁移信息,按installed_rank升序排序。AppliedMigration可以认为就是表实体
List<AppliedMigration> appliedMigrations = schemaHistory.allAppliedMigrations();
// 究其来源,还是配置中的内容,放到了上下文中。
MigrationInfoContext context = new MigrationInfoContext();
context.outOfOrder = outOfOrder;
context.pending = pending;
context.missing = missing;
context.ignored = ignored;
context.future = future;
context.target = target;
// resolvedVersioned存储了所有的类路径下的V类型迁移,并按版本号排序
Map<Pair<MigrationVersion, Boolean>, ResolvedMigration> resolvedVersioned =
new TreeMap<>();
// resolvedRepeatable存储了所有类路径下的R类型迁移,并按描述排序
Map<String, ResolvedMigration> resolvedRepeatable = new TreeMap<>();
for (ResolvedMigration resolvedMigration : resolvedMigrations) {
MigrationVersion version = resolvedMigration.getVersion();
// 当version为null说明是R类型。否则是V类型,这里维护了类路径下最新的版本。
if (version != null) {
if (version.compareTo(context.lastResolved) > 0) {
// 维护类路径下最新的版本
context.lastResolved = version;
}
// Pair的right代表了是否是undo,后面resolvedVersioned调用get时会用到
resolvedVersioned.put(Pair.of(version,false), resolvedMigration);
} else {
// 存储R类型迁移
resolvedRepeatable.put(resolvedMigration.getDescription(), resolvedMigration);
}
}
// appliedVersioned存放了所有DB应用的V类型的迁移。pair的left是db中的迁移信息,right是outOfOrder属性值,默认是false,但是如果低于了DB中应用的最新版本,就变成了true
List<Pair<AppliedMigration, AppliedMigrationAttributes>> appliedVersioned = new ArrayList<>();
// appliedRepeatable存放了DB中R类型迁移
List<AppliedMigration> appliedRepeatable = new ArrayList<>();
// 由于appliedMigrations是按照installed_rank升序排序,所以遍历顺序可以认为是已经迁移的顺序
for (AppliedMigration appliedMigration : appliedMigrations) {
MigrationVersion version = appliedMigration.getVersion();
if (version == null) {
appliedRepeatable.add(appliedMigration);
continue;
}
if (appliedMigration.getType() == MigrationType.SCHEMA) {
context.schema = version;
}
if (appliedMigration.getType() == MigrationType.BASELINE) {
context.baseline = version;
}
// DB中的信息,默认outOfOrder赋值为false(5.x版本的AppliedMigrationAttributes中仅有一个outOfOrder属性)
appliedVersioned.add(Pair.of(appliedMigration, new AppliedMigrationAttributes()));
}
// 经过循环得到db最新应用的版本context.lastApplied
for (Pair<AppliedMigration, AppliedMigrationAttributes> av : appliedVersioned) {
MigrationVersion version = av.getLeft().getVersion();
if (version != null) {
// 维护DB中最新版本
if (version.compareTo(context.lastApplied) > 0) {
context.lastApplied = version;
} else {
// 如果低于最新版本,置为true。说明是个插队的。
av.getRight().outOfOrder = true;
}
}
}
// 如果没有指定target,那么就用最新已迁移的
if (MigrationVersion.CURRENT == target) {
context.target = context.lastApplied;
}
// 这里可以认为是所有需要迁移信息,包括了undo类型的。
List<MigrationInfoImpl> migrationInfos1 = new ArrayList<>();
// pendingResolvedVersioned可以认为是后续将要进行迁移的内容。是由resolvedVersioned和appliedVersioned的差集得来,去除了undo类型的迁移。
// resolvedVersioned.values()中都是非undo类型的迁移
Set<ResolvedMigration> pendingResolvedVersioned = new HashSet<>(resolvedVersioned.values());
for (Pair<AppliedMigration, AppliedMigrationAttributes> av : appliedVersioned) {
// 找到指定版本号的迁移,要求是非undo类型
ResolvedMigration resolvedMigration = resolvedVersioned.get(Pair.of(av.getLeft().getVersion(), av.getLeft().getType().isUndo()));
if (resolvedMigration != null) {
// 这里认为是在做差集。resolvedVersioned - appliedVersioned,即是需要追加的迁移
pendingResolvedVersioned.remove(resolvedMigration);
}
// 将db中的迁移加入迁移信息中
// resolvedMigration可能为null,可能是因为是undo类型,也可能db中有,但类路径下没有
migrationInfos1.add(new MigrationInfoImpl(resolvedMigration, av.getLeft(), context, av.getRight().outOfOrder));
}
// 将db中没有的,需要额外迁移的内容加进来
// outOfOrder指定为false,因为这些是后续要追加进来的,按照顺序去迁移就好了
for (ResolvedMigration prv : pendingResolvedVersioned) {
migrationInfos1.add(new MigrationInfoImpl(prv, null, context, false));
}
// 遍历DB中的R类型迁移,如果latestRepeatableRuns中没有,或者有但是rank发生了变化,那么就加到latestRepeatableRuns中。
// 总的来说,latestRepeatableRuns中存放的是db中最新的一批R类型迁移。key是description(R类型的标识),value是rank(主键,也代表了实际应用顺序)。
for (AppliedMigration appliedRepeatableMigration : appliedRepeatable) {
if (!context.latestRepeatableRuns.containsKey(appliedRepeatableMigration.getDescription())
|| (appliedRepeatableMigration.getInstalledRank() > context.latestRepeatableRuns.get(appliedRepeatableMigration.getDescription()))) {
context.latestRepeatableRuns.put(appliedRepeatableMigration.getDescription(), appliedRepeatableMigration.getInstalledRank());
}
}
// pendingResolvedRepeatable存放需要应用的R类型迁移,存在于类路径中(实际上pending的都是类路径上的,只是和db作对比之后,类路径上的过滤掉了一批)
Set<ResolvedMigration> pendingResolvedRepeatable = new HashSet<>(resolvedRepeatable.values());
for (AppliedMigration appliedRepeatableMigration : appliedRepeatable) {
ResolvedMigration resolvedMigration = resolvedRepeatable.get(appliedRepeatableMigration.getDescription());
int latestRank = context.latestRepeatableRuns.get(appliedRepeatableMigration.getDescription());
// 这里可以认为是在取 appliedRepeatable 和 resolvedRepeatable的差集.
// pendingResolvedRepeatable使用了全量类路径下的R类型做初值,删去的是类路径和DB对比,无任何变更的R类型,那么剩余的就是需要追加应用的。
// 变更考察的点 1.rank有无变化(与最新相比,对于R类型,也只有和db中最新的rank相比才有意义)2.内容有无变化
if (resolvedMigration != null && appliedRepeatableMigration.getInstalledRank() == latestRank && ObjectUtils.nullSafeEquals(appliedRepeatableMigration.getChecksum(), resolvedMigration.getChecksum())) {
pendingResolvedRepeatable.remove(resolvedMigration);
}
// 增加db中的
migrationInfos1.add(new MigrationInfoImpl(resolvedMigration, appliedRepeatableMigration, context, false));
}
for (ResolvedMigration prr : pendingResolvedRepeatable) {
// 增加pending的
migrationInfos1.add(new MigrationInfoImpl(prr, null, context, false));
}
// 注意:如果是db里的,是有installedRank的,那么按照此字段排序。其余的整体是按照版本号排序,部分会受迁移状态MigrationState影响
Collections.sort(migrationInfos1);
migrationInfos = migrationInfos1;
}
DB中最新版本
org.flywaydb.core.internal.info.MigrationInfoServiceImpl#current
public MigrationInfo current() {
MigrationInfo current = null;
// 遍历所有信息
for (MigrationInfoImpl migrationInfo : migrationInfos) {
// 当前信息需要满足:1.被应用过 2.是V类型迁移 3.1和2中版本最高的那个
if (migrationInfo.getState().isApplied()
&& migrationInfo.getVersion() != null
&& (current == null || migrationInfo.getVersion().compareTo(current.getVersion()) > 0)) {
current = migrationInfo;
}
}
if (current != null) {
return current;
}
// 找不到V类型,就找R类型的
for (int i = migrationInfos.size() - 1; i >= 0; i--) {
MigrationInfoImpl migrationInfo = migrationInfos.get(i);
if (migrationInfo.getState().isApplied()) {
return migrationInfo;
}
}
return null;
}