启动项目时flyway报错
出现此情况时通常是对flyway的历史sql文件进行了修改
报错源码阅读:
1.flyway类
此方法在org.flywaydb.core包的Flyway类中
public MigrateResult migrate() throws FlywayException {return execute(new Command<MigrateResult>() {public MigrateResult execute(MigrationResolver migrationResolver,SchemaHistory schemaHistory, Database database, Schema[] schemas, CallbackExecutor callbackExecutor,StatementInterceptor statementInterceptor) {if (configuration.isValidateOnMigrate()) {ValidateResult validateResult = doValidate(database, migrationResolver, schemaHistory, schemas, callbackExecutor,true);if (!validateResult.validationSuccessful && !configuration.isCleanOnValidationError()) {throw new FlywayValidateException(validateResult.errorDetails);}}if (!schemaHistory.exists()) {List<Schema> nonEmptySchemas = new ArrayList<>();for (Schema schema : schemas) {if (schema.exists() && !schema.empty()) {nonEmptySchemas.add(schema);}}if (!nonEmptySchemas.isEmpty()) {if (configuration.isBaselineOnMigrate()) {doBaseline(schemaHistory, callbackExecutor, database);} else {// Second check for MySQL which is sometimes flaky otherwiseif (!schemaHistory.exists()) {throw new FlywayException("Found non-empty schema(s) "+ StringUtils.collectionToCommaDelimitedString(nonEmptySchemas)+ " but no schema history table. Use baseline()"+ " or set baselineOnMigrate to true to initialize the schema history table.");}}} else {if (configuration.getCreateSchemas()) {new DbSchemas(database, schemas, schemaHistory).create(false);} else {LOG.warn("The configuration option 'createSchemas' is false.\n" +"However the schema history table still needs a schema to reside in.\n" +"You must manually create a schema for the schema history table to reside in.\n" +"See http://flywaydb.org/documentation/migrations#the-createschemas-option-and-the-schema-history-table)");}schemaHistory.create(false);}}return new DbMigrate(database, schemaHistory, schemas, migrationResolver, configuration,callbackExecutor).migrate();}}, true);}
2.configuration.isValidateOnMigrate()配置
第一部分代码:
首先configuration.isValidateOnMigrate()从配置中获取是否在迁移时进行验证(默认为true)
if (configuration.isValidateOnMigrate()) {ValidateResult validateResult = doValidate(database, migrationResolver, schemaHistory, schemas, callbackExecutor,true);if (!validateResult.validationSuccessful && !configuration.isCleanOnValidationError()) {throw new FlywayValidateException(validateResult.errorDetails);} }
3. doValidate()
doValidate方法进行验证:
private ValidateResult doValidate(Database database,MigrationResolver migrationResolver,SchemaHistory schemaHistory,Schema[] schemas, CallbackExecutor callbackExecutor, boolean ignorePending) {ValidateResult validateResult = new DbValidate(database, schemaHistory, schemas, migrationResolver,configuration, ignorePending, callbackExecutor).validate();if (!validateResult.validationSuccessful && configuration.isCleanOnValidationError()) {doClean(database, schemaHistory, schemas, callbackExecutor);}return validateResult; }
4.DbValidate类validate()方法
最终验证逻辑在DbValidate类的validate方法中:
public ValidateResult validate() {
CommandResultFactory commandResultFactory = new CommandResultFactory();
if (!schema.exists()) {
if (!migrationResolver.resolveMigrations(new Context() {
@Override
public Configuration getConfiguration() {
return configuration;
}
}).isEmpty() && !pending) {
String validationErrorMessage = "Schema " + schema + " doesn't exist yet";
ErrorDetails validationError = new ErrorDetails(ErrorCode.SCHEMA_DOES_NOT_EXIST, validationErrorMessage);
return commandResultFactory.createValidateResult(database.getCatalog(), validationError, 0, null, new ArrayList<>());
}
return commandResultFactory.createValidateResult(database.getCatalog(), null, 0, null, new ArrayList<>());
}callbackExecutor.onEvent(Event.BEFORE_VALIDATE);
LOG.debug("Validating migrations ...");
#计时器,用于计算迁移时间
StopWatch stopWatch = new StopWatch();
stopWatch.start();Pair<Integer, List<ValidateOutput>> result = ExecutionTemplateFactory.createExecutionTemplate(connection.getJdbcConnection(),
database).execute(new Callable<Pair<Integer, List<ValidateOutput>>>() {
@Override
public Pair<Integer, List<ValidateOutput>> call() {
MigrationInfoServiceImpl migrationInfoService =
new MigrationInfoServiceImpl(migrationResolver, schemaHistory, schemas, database, configuration,
configuration.getTarget(),
configuration.isOutOfOrder(),
configuration.getCherryPick(),
pending,
configuration.isIgnoreMissingMigrations(),
configuration.isIgnoreIgnoredMigrations(),
configuration.isIgnoreFutureMigrations());migrationInfoService.refresh();
int count = migrationInfoService.all().length;
List<ValidateOutput> invalidMigrations = migrationInfoService.validate();
return Pair.of(count, invalidMigrations);
}
});stopWatch.stop();
List<String> warnings = new ArrayList<>();
List<ValidateOutput> invalidMigrations = result.getRight();
ErrorDetails validationError = null;
int count = 0;
if (invalidMigrations.isEmpty()) {
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())));if (count == 0) {
String noMigrationsWarning = "No migrations found. Are your locations set up correctly?";
warnings.add(noMigrationsWarning);
LOG.warn(noMigrationsWarning);
}
}
callbackExecutor.onEvent(Event.AFTER_VALIDATE);
} else {
validationError = new ErrorDetails(ErrorCode.VALIDATE_ERROR, "Migrations have failed validation");
callbackExecutor.onEvent(Event.AFTER_VALIDATE_ERROR);
}ValidateResult validateResult = commandResultFactory.createValidateResult(database.getCatalog(), validationError, count, invalidMigrations, warnings);
return validateResult;
}
4.1 schema.exists()
if (!schema.exists()) { //在schema不存在的情况下,这行代码尝试解析数据库迁移脚本。如果存在未应用的迁移脚本且不是挂起状态(pending),则生成一个错误消息,指示schema尚不存在。 创建一个包含错误代码和消息的ErrorDetails对象,用于指示schema不存在的错误。if (!migrationResolver.resolveMigrations(new Context() {@Overridepublic Configuration getConfiguration() {return configuration;}}).isEmpty() && !pending) {String validationErrorMessage = "Schema " + schema + " doesn't exist yet";ErrorDetails validationError = new ErrorDetails(ErrorCode.SCHEMA_DOES_NOT_EXIST,validationErrorMessage);return commandResultFactory.createValidateResult(database.getCatalog(), validationError, 0, null, new ArrayList<>());} //如果schema存在或没有未应用的迁移脚本,返回一个验证结果,其中不包含错误信息。 return commandResultFactory.createValidateResult(database.getCatalog(),null,0,null, new ArrayList<>()); }
在schema不存在的情况下,这行代码尝试解析数据库迁移脚本。如果存在未应用的迁移脚本且不是挂起状态(pending),则生成一个错误消息,指示schema尚不存在。 创建一个包含错误代码和消息的ErrorDetails对象,用于指示schema不存在的错误。
如果schema存在或没有未应用的迁移脚本,返回一个验证结果,其中不包含错误信息。
5.refresh()
这段代码主要在refresh方法
Pair<Integer, List<ValidateOutput>> result = ExecutionTemplateFactory.createExecutionTemplate(connection.getJdbcConnection(),database).execute(new Callable<Pair<Integer, List<ValidateOutput>>>() {@Overridepublic Pair<Integer, List<ValidateOutput>> call() {MigrationInfoServiceImpl migrationInfoService =new MigrationInfoServiceImpl(migrationResolver, schemaHistory, schemas, database, configuration,configuration.getTarget(),configuration.isOutOfOrder(),configuration.getCherryPick(),pending,configuration.isIgnoreMissingMigrations(),configuration.isIgnoreIgnoredMigrations(),configuration.isIgnoreFutureMigrations());migrationInfoService.refresh();int count = migrationInfoService.all().length;List<ValidateOutput> invalidMigrations = migrationInfoService.validate();return Pair.of(count, invalidMigrations);} });
public void refresh() {Collection<ResolvedMigration> resolvedMigrations = migrationResolver.resolveMigrations(context);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;context.cherryPick = cherryPick;Map<Pair<MigrationVersion, Boolean>, ResolvedMigration> resolvedVersioned = new TreeMap<>();Map<String, ResolvedMigration> resolvedRepeatable = new TreeMap<>();// Separate resolved migrations into versioned and repeatablefor (ResolvedMigration resolvedMigration : resolvedMigrations) {MigrationVersion version = resolvedMigration.getVersion();if (version != null) {if (version.compareTo(context.lastResolved) > 0) {context.lastResolved = version;}//noinspection RedundantConditionalExpressionresolvedVersioned.put(Pair.of(version,false), resolvedMigration);} else {resolvedRepeatable.put(resolvedMigration.getDescription(), resolvedMigration);}}
// Split applied into version and repeatable, and update state from synthetic migrationsList<Pair<AppliedMigration, AppliedMigrationAttributes>> appliedVersioned = new ArrayList<>();List<Pair<AppliedMigration, AppliedMigrationAttributes>> appliedRepeatable = new ArrayList<>();for (AppliedMigration appliedMigration : appliedMigrations) {MigrationVersion version = appliedMigration.getVersion();if (version == null) {appliedRepeatable.add(Pair.of(appliedMigration, new AppliedMigrationAttributes()));if (appliedMigration.getType().equals(MigrationType.DELETE) && appliedMigration.isSuccess()) {markRepeatableAsDeleted(appliedMigration.getDescription(), appliedRepeatable);}continue;}if (appliedMigration.getType() == MigrationType.SCHEMA) {context.schema = version;}if (appliedMigration.getType() == MigrationType.BASELINE) {context.baseline = version;}if (appliedMigration.getType().equals(MigrationType.DELETE) && appliedMigration.isSuccess()) {markAsDeleted(version, appliedVersioned);}appliedVersioned.add(Pair.of(appliedMigration, new AppliedMigrationAttributes()));}// Update last applied and out of order statesfor (Pair<AppliedMigration, AppliedMigrationAttributes> av : appliedVersioned) {MigrationVersion version = av.getLeft().getVersion();if (version != null) {if (version.compareTo(context.lastApplied) > 0) {if (av.getLeft().getType() != MigrationType.DELETE && !av.getRight().deleted) {context.lastApplied = version;}} else {av.getRight().outOfOrder = true;}}}// Set targetif (MigrationVersion.CURRENT == target) {context.target = context.lastApplied;}// Identify pending versioned migrations and build output migration info listList<MigrationInfoImpl> migrationInfos1 = new ArrayList<>();Set<ResolvedMigration> pendingResolvedVersioned = new HashSet<>(resolvedVersioned.values());for (Pair<AppliedMigration, AppliedMigrationAttributes> av : appliedVersioned) {ResolvedMigration resolvedMigration = resolvedVersioned.get(Pair.of(av.getLeft().getVersion(), av.getLeft().getType().isUndo()));// Remove pending migrationsif (resolvedMigration != null&& !av.getRight().deleted && av.getLeft().getType() != MigrationType.DELETE) {pendingResolvedVersioned.remove(resolvedMigration);}// Build final migration infomigrationInfos1.add(new MigrationInfoImpl(resolvedMigration, av.getLeft(), context, av.getRight().outOfOrder, av.getRight().deleted));}// Add all pending migrations to output listfor (ResolvedMigration prv : pendingResolvedVersioned) {migrationInfos1.add(new MigrationInfoImpl(prv, null, context, false, false));}if (target != null && target != MigrationVersion.CURRENT && target != MigrationVersion.LATEST) {boolean targetFound = false;for (MigrationInfoImpl migration : migrationInfos1) {if (target.compareTo(migration.getVersion()) == 0) {targetFound = true;break;}}if (!targetFound) {throw new FlywayException("No migration with a target version " + target + " could be found. Ensure target is specified correctly and the migration exists.");}}// Setup the latest repeatable run ranksfor (Pair<AppliedMigration, AppliedMigrationAttributes> av : appliedRepeatable) {if (av.getRight().deleted && av.getLeft().getType() == MigrationType.DELETE) {continue;}AppliedMigration appliedRepeatableMigration = av.getLeft();String desc = appliedRepeatableMigration.getDescription();int rank = appliedRepeatableMigration.getInstalledRank();Map<String, Integer> latestRepeatableRuns = context.latestRepeatableRuns;if (!latestRepeatableRuns.containsKey(desc) || (rank > latestRepeatableRuns.get(desc))) {latestRepeatableRuns.put(desc, rank);}}// Using latest repeatable runs, discover pending repeatables and build output listSet<ResolvedMigration> pendingResolvedRepeatable = new HashSet<>(resolvedRepeatable.values());for (Pair<AppliedMigration, AppliedMigrationAttributes> av : appliedRepeatable) {AppliedMigration appliedRepeatableMigration = av.getLeft();String desc = appliedRepeatableMigration.getDescription();int rank = appliedRepeatableMigration.getInstalledRank();ResolvedMigration resolvedMigration = resolvedRepeatable.get(desc);int latestRank = context.latestRepeatableRuns.get(desc);// If latest run is the same rank, its not pendingif (!av.getRight().deleted && av.getLeft().getType() != MigrationType.DELETE&& resolvedMigration != null && rank == latestRank && resolvedMigration.checksumMatches(appliedRepeatableMigration.getChecksum())) {pendingResolvedRepeatable.remove(resolvedMigration);}// Add to output listmigrationInfos1.add(new MigrationInfoImpl(resolvedMigration, appliedRepeatableMigration, context, false, av.getRight().deleted));}// Add pending repeatables to output listfor (ResolvedMigration prr : pendingResolvedRepeatable) {migrationInfos1.add(new MigrationInfoImpl(prr, null, context, false, false));}// Update whether all managed schemas are empty or notallSchemasEmpty = Arrays.stream(schemas).allMatch(Schema::empty);// Set outputCollections.sort(migrationInfos1);migrationInfos = migrationInfos1;}
5.1 将已解析的迁移(migrations)分为有版本号的迁移(versioned)和可重复的迁移(repeatable)
// Separate resolved migrations into versioned and repeatable
for (ResolvedMigration resolvedMigration : resolvedMigrations) {
MigrationVersion version = resolvedMigration.getVersion();
if (version != null) {
if (version.compareTo(context.lastResolved) > 0) {
context.lastResolved = version;
}
//noinspection RedundantConditionalExpression
resolvedVersioned.put(Pair.of(version, false), resolvedMigration);
} else {
resolvedRepeatable.put(resolvedMigration.getDescription(), resolvedMigration);
}
}
这段代码在Flyway中用于将已解析的迁移(migrations)分为有版本号的迁移(versioned)和可重复的迁移(repeatable)。让我们逐步解读其含义:
1. for (ResolvedMigration resolvedMigration : resolvedMigrations) {
:
这行代码开始一个循环,遍历所有已解析的迁移。
2. MigrationVersion version = resolvedMigration.getVersion();
:
从已解析的迁移中获取迁移的版本号。
3. if (version != null) {
:
检查迁移是否有版本号。如果有版本号,表示这是一个有版本号的迁移。
4. if (version.compareTo(context.lastResolved) > 0) {
:
比较当前迁移的版本号与上一次解析的迁移的版本号。如果当前迁移的版本号比上一次解析的迁移版本号大,则更新 context.lastResolved
为当前迁移的版本号。
5. resolvedVersioned.put(Pair.of(version, false), resolvedMigration);
:
将有版本号的迁移添加到 resolvedVersioned
映射中,使用版本号作为键,对应的已解析迁移作为值。
6. } else {
:
如果迁移没有版本号,则表示这是一个可重复的迁移。
7. resolvedRepeatable.put(resolvedMigration.getDescription(), resolvedMigration);
:将可重复的迁移添加到 resolvedRepeatable
映射中,使用迁移的描述(description)作为键,对应的已解析迁移作为值。 通过这段代码,Flyway将已解析的迁移根据是否有版本号进行分类,分别存储到 resolvedVersioned
和 resolvedRepeatable
映射中,以便后续执行数据库迁移操作时能够区分处理有版本号和可重复的迁移。
5.2 将已应用的迁移(applied migrations)分为有版本号的迁移(versioned)和可重复的迁移(repeatable),并根据迁移类型更新状态。
// Split applied into version and repeatable, and update state from synthetic migrations List<Pair<AppliedMigration, AppliedMigrationAttributes>> appliedVersioned = new ArrayList<>(); List<Pair<AppliedMigration, AppliedMigrationAttributes>> appliedRepeatable = new ArrayList<>(); for (AppliedMigration appliedMigration : appliedMigrations) {MigrationVersion version = appliedMigration.getVersion();if (version == null) {appliedRepeatable.add(Pair.of(appliedMigration, new AppliedMigrationAttributes()));if (appliedMigration.getType().equals(MigrationType.DELETE) && appliedMigration.isSuccess()) {markRepeatableAsDeleted(appliedMigration.getDescription(), appliedRepeatable);}continue;}if (appliedMigration.getType() == MigrationType.SCHEMA) {context.schema = version;}if (appliedMigration.getType() == MigrationType.BASELINE) {context.baseline = version;}if (appliedMigration.getType().equals(MigrationType.DELETE) && appliedMigration.isSuccess()) {markAsDeleted(version, appliedVersioned);}appliedVersioned.add(Pair.of(appliedMigration, new AppliedMigrationAttributes())); }
1. List<Pair<AppliedMigration, AppliedMigrationAttributes>> appliedVersioned = new ArrayList<>();
和 List<Pair<AppliedMigration, AppliedMigrationAttributes>> appliedRepeatable = new ArrayList<>();
:
创建两个空列表,用于存储有版本号的已应用迁移和可重复的已应用迁移。
2. for (AppliedMigration appliedMigration : appliedMigrations) {
:
开始遍历所有已应用的迁移。
3. MigrationVersion version = appliedMigration.getVersion();
:
获取当前迁移的版本号。 4. if (version == null) {
:
检查当前迁移是否有版本号。如果没有版本号,表示这是一个可重复的迁移。
5. appliedRepeatable.add(Pair.of(appliedMigration, new AppliedMigrationAttributes()));
:
将可重复的迁移添加到 appliedRepeatable
列表中,并创建一个新的 AppliedMigrationAttributes
对象。
6. if (appliedMigration.getType().equals(MigrationType.DELETE) && appliedMigration.isSuccess()) {
:
检查可重复的迁移是否为删除类型且成功应用。如果是,则调用 markRepeatableAsDeleted
方法标记该迁移为已删除。
7. continue;
:
继续下一次循环,处理下一个已应用迁移。
8. 如果迁移有版本号,则继续执行以下逻辑:
- 如果迁移类型为 MigrationType.SCHEMA
,将 context.schema
更新为当前迁移的版本号。
- 如果迁移类型为 MigrationType.BASELINE
,将 context.baseline
更新为当前迁移的版本号。
- 如果迁移类型为删除且成功应用,调用 markAsDeleted
方法标记该版本号的迁移为已删除。
- 将有版本号的迁移添加到 appliedVersioned
列表中,并创建一个新的AppliedMigrationAttributes
对象。
通过这段代码,Flyway将已应用的迁移根据是否有版本号进行分类,并根据迁移类型更新相关状态信息,以便在后续的数据库迁移操作中正确处理已应用的迁移。
5.3 用于更新最后应用的迁移版本和处理未按顺序应用的迁移。
// Update last applied and out of order states for (Pair<AppliedMigration, AppliedMigrationAttributes> av : appliedVersioned) {MigrationVersion version = av.getLeft().getVersion();if (version != null) {if (version.compareTo(context.lastApplied) > 0) {if (av.getLeft().getType() != MigrationType.DELETE && !av.getRight().deleted) {context.lastApplied = version;}} else {av.getRight().outOfOrder = true;}} }
1. MigrationVersion version = av.getLeft().getVersion();
:
获取当前迁移的版本号。
2. if (version != null) {
:检查当前迁移是否有版本号。
3. if (version.compareTo(context.lastApplied) > 0) {
:
如果当前迁移的版本号大于上次应用的迁移版本号,则执行以下逻辑:
- if (av.getLeft().getType() != MigrationType.DELETE && !av.getRight().deleted)
:
检查当前迁移是否不是删除类型且未被标记为已删除。
- context.lastApplied = version;
:更新 context.lastApplied
为当前迁移的版本号。
4. 如果当前迁移的版本号不大于上次应用的迁移版本号,则执行以下逻辑:
- av.getRight().outOfOrder = true;
:将当前迁移标记为未按顺序应用。 通过这段代码,Flyway更新了最后应用的迁移版本,并处理了未按顺序应用的迁移。这有助于跟踪数据库迁移的状态,并处理可能存在的未按顺序应用的迁移。
5.4 Flyway根据目标版本的设定,将目标版本设置为当前数据库的最后应用的迁移版本。这有助于确定下一步要执行的数据库迁移操作,以使数据库状态达到目标版本
// Set target if (MigrationVersion.CURRENT == target) {context.target = context.lastApplied; }
1. if (MigrationVersion.CURRENT == target) {
:
这行代码检查目标版本是否设置为 CURRENT
,即表示目标版本为当前版本。
2. context.target = context.lastApplied;
:
如果目标版本设置为 CURRENT
,则将目标版本设置为上次应用的迁移版本。这意味着Flyway将把当前数据库的状态作为目标版本,即目标版本与最后一次成功应用的迁移版本相同。
5.5 Flyway识别出待处理的有版本号的迁移,构建包含这些迁移信息的迁移信息对象,并将其添加到迁移信息列表中。这有助于确定哪些迁移需要被处理以使数据库达到目标版本。
// Identify pending versioned migrations and build output migration info list List<MigrationInfoImpl> migrationInfos1 = new ArrayList<>(); Set<ResolvedMigration> pendingResolvedVersioned = new HashSet<>(resolvedVersioned.values()); for (Pair<AppliedMigration, AppliedMigrationAttributes> av : appliedVersioned) {ResolvedMigration resolvedMigration = resolvedVersioned.get(Pair.of(av.getLeft().getVersion(), av.getLeft().getType().isUndo()));// Remove pending migrationsif (resolvedMigration != null&& !av.getRight().deleted && av.getLeft().getType() != MigrationType.DELETE) {pendingResolvedVersioned.remove(resolvedMigration);}// Build final migration infomigrationInfos1.add(new MigrationInfoImpl(resolvedMigration, av.getLeft(), context, av.getRight().outOfOrder, av.getRight().deleted)); }
1. List<MigrationInfoImpl> migrationInfos1 = new ArrayList<>();
:
创建一个空的迁移信息列表,用于存储构建的迁移信息对象。
2. Set<ResolvedMigration> pendingResolvedVersioned = new HashSet<>(resolvedVersioned.values());
:
创建一个包含所有有版本号的待处理迁移的集合,初始值为所有已解析的有版本号的迁移。
3. for (Pair<AppliedMigration, AppliedMigrationAttributes> av : appliedVersioned) {
:开始遍历已应用的有版本号的迁移。
4. ResolvedMigration resolvedMigration = resolvedVersioned.get(Pair.of(av.getLeft().getVersion(), av.getLeft().getType().isUndo()));
:
根据已应用迁移的版本号和类型,从已解析的有版本号的迁移中获取对应的解析迁移对象。
5. 如果找到对应的解析迁移对象,则执行以下逻辑:
- pendingResolvedVersioned.remove(resolvedMigration);
:
从待处理的有版本号的迁移集合中移除该解析迁移对象。 - 构建最终的迁移信息对象并添加到迁移信息列表中:
5.6 Flyway将所有待处理的有版本号的迁移创建为迁移信息对象,并添加到迁移信息列表中
// Add all pending migrations to output list for (ResolvedMigration prv : pendingResolvedVersioned) {migrationInfos1.add(new MigrationInfoImpl(prv, null, context, false, false)); }
1. for (ResolvedMigration prv : pendingResolvedVersioned) {
:
开始遍历所有待处理的有版本号的迁移。
2. migrationInfos1.add(new MigrationInfoImpl(prv, null, context, false, false));
:
为每个待处理的有版本号的迁移创建一个迁移信息对象,并将其添加到迁移信息列表中。
- prv
:表示当前待处理的有版本号的迁移对象。
- null
:表示已应用的迁移对象为空,因为这是待处理的迁移。
- context
:表示上下文对象,包含有关数据库迁移的信息。
- false, false
:分别表示迁移不是未按顺序应用的和未被标记为已删除的。
通过这段代码,Flyway将所有待处理的有版本号的迁移创建为迁移信息对象,并添加到迁移信息列表中。这有助于对待处理的迁移进行跟踪和管理,以便后续执行数据库迁移操作。
5.7 Flyway确保目标版本存在于待处理的迁移信息列表中,以便在执行数据库迁移操作时能够准确地将数据库状态迁移到目标版本
if (target != null && target != MigrationVersion.CURRENT && target != MigrationVersion.LATEST) {boolean targetFound = false;for (MigrationInfoImpl migration : migrationInfos1) {if (target.compareTo(migration.getVersion()) == 0) {targetFound = true;break;}}if (!targetFound) {throw new FlywayException("No migration with a target version " + target + " could be found. Ensure target is specified correctly and the migration exists.");}
}
1. if (target != null && target != MigrationVersion.CURRENT && target != MigrationVersion.LATEST) {
:
这行代码检查目标版本是否已指定且不等于 CURRENT
或 LATEST
。这表示要求目标版本不是当前版本或最新版本,而是一个具体的版本号。
2. boolean targetFound = false;
:
初始化一个布尔变量 targetFound
,用于标记是否找到与目标版本匹配的迁移信息。
3. for (MigrationInfoImpl migration : migrationInfos1) {
:
开始遍历待处理的迁移信息列表。
4. if (target.compareTo(migration.getVersion()) == 0) {
:
检查当前迁移信息对象的版本号是否与目标版本相匹配。
5. 如果找到与目标版本匹配的迁移信息,则执行以下逻辑: - targetFound = true;
:
将 targetFound
标记为 true
,表示找到了与目标版本匹配的迁移信息。 - 跳出循环。
6. 如果未找到与目标版本匹配的迁移信息,则抛出 FlywayException
异常:
- throw new FlywayException("No migration with a target version " + target + " could be found. Ensure target is specified correctly and the migration exists.");
:
抛出异常,指示未找到与目标版本匹配的迁移信息。提示用户检查目标版本是否正确指定,并确保该迁移存在。
通过这段代码,Flyway确保目标版本存在于待处理的迁移信息列表中,以便在执行数据库迁移操作时能够准确地将数据库状态迁移到目标版本。
5.8 Flyway设置了最新可重复迁移的运行顺序,确保在处理可重复迁移时能够跟踪并记录最新的运行顺序,以便在下次迁移时正确处理可重复迁移
// Setup the latest repeatable run ranks for (Pair<AppliedMigration, AppliedMigrationAttributes> av : appliedRepeatable) {if (av.getRight().deleted && av.getLeft().getType() == MigrationType.DELETE) {continue;}AppliedMigration appliedRepeatableMigration = av.getLeft();String desc = appliedRepeatableMigration.getDescription();int rank = appliedRepeatableMigration.getInstalledRank();Map<String, Integer> latestRepeatableRuns = context.latestRepeatableRuns;if (!latestRepeatableRuns.containsKey(desc) || (rank > latestRepeatableRuns.get(desc))) {latestRepeatableRuns.put(desc, rank);} }
1. for (Pair<AppliedMigration, AppliedMigrationAttributes> av : appliedRepeatable) {
:
开始遍历已应用的可重复迁移。
2. if (av.getRight().deleted && av.getLeft().getType() == MigrationType.DELETE) { continue; }
:
如果可重复迁移被标记为已删除且类型为删除,则跳过该迁移,不处理。
3. AppliedMigration appliedRepeatableMigration = av.getLeft();
:
获取当前可重复迁移的应用迁移对象。
4. String desc = appliedRepeatableMigration.getDescription();
:
获取当前可重复迁移的描述(description)。
5. int rank = appliedRepeatableMigration.getInstalledRank();
:
获取当前可重复迁移的安装顺序(installed rank)。
6. Map<String, Integer> latestRepeatableRuns = context.latestRepeatableRuns;
:
获取用于存储最新可重复运行顺序的映射对象。
7. 如果当前描述在 latestRepeatableRuns
中不存在,或者当前可重复迁移的运行顺序比已记录的运行顺序更大,则执行以下逻辑: - latestRepeatableRuns.put(desc, rank);
:将当前可重复迁移的描述和运行顺序存入 latestRepeatableRuns
映射中,更新最新可重复迁移的运行顺序。
通过这段代码,Flyway设置了最新可重复迁移的运行顺序,确保在处理可重复迁移时能够跟踪并记录最新的运行顺序,以便在下次迁移时正确处理可重复迁移。
5.9 Flyway根据最新的可重复运行顺序,发现待处理的可重复迁移,并构建包含这些迁移信息的迁移信息对象,以便后续处理和执行数据库迁移操作
// Using latest repeatable runs, discover pending repeatables and build output list Set<ResolvedMigration> pendingResolvedRepeatable = new HashSet<>(resolvedRepeatable.values()); for (Pair<AppliedMigration, AppliedMigrationAttributes> av : appliedRepeatable) {AppliedMigration appliedRepeatableMigration = av.getLeft();String desc = appliedRepeatableMigration.getDescription();int rank = appliedRepeatableMigration.getInstalledRank();ResolvedMigration resolvedMigration = resolvedRepeatable.get(desc);int latestRank = context.latestRepeatableRuns.get(desc);// If latest run is the same rank, its not pendingif (!av.getRight().deleted && av.getLeft().getType() != MigrationType.DELETE&& resolvedMigration != null && rank == latestRank && resolvedMigration.checksumMatches(appliedRepeatableMigration.getChecksum())) {pendingResolvedRepeatable.remove(resolvedMigration);}// Add to output listmigrationInfos1.add(new MigrationInfoImpl(resolvedMigration, appliedRepeatableMigration, context, false, av.getRight().deleted)); }
1. Set<ResolvedMigration> pendingResolvedRepeatable = new HashSet<>(resolvedRepeatable.values());
:
创建一个包含所有待处理可重复迁移的集合,初始值为所有已解析的可重复迁移。
2. for (Pair<AppliedMigration, AppliedMigrationAttributes> av : appliedRepeatable) {
:开始遍历已应用的可重复迁移。
3. AppliedMigration appliedRepeatableMigration = av.getLeft();
:
获取当前可重复迁移的应用迁移对象。
4. String desc = appliedRepeatableMigration.getDescription();
:
获取当前可重复迁移的描述。
5. int rank = appliedRepeatableMigration.getInstalledRank();
:
获取当前可重复迁移的安装顺序。
6. ResolvedMigration resolvedMigration = resolvedRepeatable.get(desc);
:从已解析的可重复迁移中获取对应的解析迁移对象。
7. int latestRank = context.latestRepeatableRuns.get(desc);
:
获取最新的可重复运行顺序。 8. 如果当前可重复迁移是未删除且不是删除类型,并且解析迁移存在且安装顺序与最新顺序相同且校验和匹配,则执行以下逻辑:
- pendingResolvedRepeatable.remove(resolvedMigration);
:
从待处理的可重复迁移集合中移除该解析迁移对象。
9. 构建最终的迁移信息对象并添加到迁移信息列表中:
- migrationInfos1.add(new MigrationInfoImpl(resolvedMigration, appliedRepeatableMigration, context, false, av.getRight().deleted));
:
为当前可重复迁移创建一个迁移信息对象,并将其添加到迁移信息列表中。
通过这段代码,Flyway根据最新的可重复运行顺序,发现待处理的可重复迁移,并构建包含这些迁移信息的迁移信息对象,以便后续处理和执行数据库迁移操作。
5.10
// Add pending repeatables to output list for (ResolvedMigration prr : pendingResolvedRepeatable) {migrationInfos1.add(new MigrationInfoImpl(prr, null, context, false, false)); }
1. for (ResolvedMigration prr : pendingResolvedRepeatable) {
:
开始遍历所有待处理的可重复迁移。
2. migrationInfos1.add(new MigrationInfoImpl(prr, null, context, false, false));
:
为每个待处理的可重复迁移创建一个迁移信息对象,并将其添加到迁移信息列表中。
- prr
:表示当前待处理的可重复迁移对象。
- null
:表示已应用的迁移对象为空,因为这是待处理的迁移。
- context
:表示上下文对象,包含有关数据库迁移的信息。
- false, false
:分别表示迁移不是未按顺序应用的和未被标记为已删除的。
通过这段代码,Flyway将所有待处理的可重复迁移创建为迁移信息对象,并添加到迁移信息列表中。这有助于对待处理的迁移进行跟踪和管理,以便后续执行数据库迁移操作。
可以看出refresh方法主要是对旧的迁移信息进行验证,编排,管理。
6.migrationInfoService.validate()
refresh方法执行完后,开始执行真正的验证逻辑
该方法会遍历所有迁移信息对象,对每个对象执行验证操作,如果存在验证错误,则将其记录到 invalidMigrations
列表中,
/*** Validate all migrations for consistency.** @return The list of migrations that failed validation, which is empty if everything is fine.*/
public List<ValidateOutput> validate() {List<ValidateOutput> invalidMigrations = new ArrayList<>();CommandResultFactory commandResultFactory = new CommandResultFactory();for (MigrationInfoImpl migrationInfo : migrationInfos) {ErrorDetails validateError = migrationInfo.validate();if (validateError != null) {invalidMigrations.add(commandResultFactory.createValidateOutput(migrationInfo,validateError));}}return invalidMigrations;
}
1. public List<ValidateOutput> validate() {
:
定义了一个公共方法 validate()
,返回一个 List
类型的 ValidateOutput
对象列表。
2. List<ValidateOutput> invalidMigrations = new ArrayList<>();
:
创建一个空列表 invalidMigrations
,用于存储验证失败的迁移信息。
3. CommandResultFactory commandResultFactory = new CommandResultFactory();
:
创建一个 CommandResultFactory
对象,用于生成命令结果。
4. for (MigrationInfoImpl migrationInfo : migrationInfos) {
:
遍历所有迁移信息对象。
5. ErrorDetails validateError = migrationInfo.validate();
:
对每个迁移信息对象执行验证操作,返回可能的验证错误信息。
6. if (validateError != null) {
:
如果存在验证错误信息,则执行以下逻辑:
- invalidMigrations.add(commandResultFactory.createValidateOutput(migrationInfo, validateError));
:
将验证失败的迁移信息和对应的验证错误信息添加到 invalidMigrations
列表中。
7. return invalidMigrations;
:
返回包含验证失败的迁移信息的列表。
通过这段代码,该方法会遍历所有迁移信息对象,对每个对象执行验证操作,如果存在验证错误,则将其记录到 invalidMigrations
列表中,并最终返回该列表。这有助于识别数据库迁移中存在的问题,以便进一步处理和修复。
7.migrationInfo.validate()
从6的代码中进入migrationInfo.validate():
// 如果迁移状态为 ABOVE_TARGET ,表示迁移在目标版本之上,直接返回 null if (MigrationState.ABOVE_TARGET.equals(state)) {return null; }// 如果迁移状态为已删除,直接返回 if (MigrationState.DELETED.equals(state)) {return null; }
7.1 Flyway在发现迁移失败时,根据迁移是否有版本号来生成相应的错误消息并返回适当的错误代码,以指导用户如何处理和修复失败的迁移
if (state.isFailed() && (!context.future || MigrationState.FUTURE_FAILED != state)) {if (getVersion() == null) {String errorMessage = "Detected failed repeatable migration: " + getDescription() + ". Please remove any half-completed changes then run repair to fix the schema history.";return new ErrorDetails(ErrorCode.FAILED_REPEATABLE_MIGRATION, errorMessage);}String errorMessage = "Detected failed migration to version " + getVersion() + " (" + getDescription() + ")" + ". Please remove any half-completed changes then run repair to fix the schema history.";return new ErrorDetails(ErrorCode.FAILED_VERSIONED_MIGRATION, errorMessage); }
1. if (state.isFailed() && (!context.future || MigrationState.FUTURE_FAILED != state)) {
:
如果迁移状态为失败且不是未来失败状态,
2. 如果当前迁移没有版本号(即可重复迁移),
- String errorMessage = "Detected failed repeatable migration: " + getDescription() + ". Please remove any half-completed changes then run repair to fix the schema history.";
:
创建错误消息,指示检测到失败的可重复迁移,请移除任何未完成的更改,然后运行修复以修复历史记录。
- return new ErrorDetails(ErrorCode.FAILED_REPEATABLE_MIGRATION, errorMessage);
:
返回一个包含错误代码和错误消息的 ErrorDetails
对象,表示检测到失败的可重复迁移。
3. 如果当前迁移有版本号(即有版本号的迁移),则执行以下逻辑:
- String errorMessage = "Detected failed migration to version " + getVersion() + " (" + getDescription() + ")" + ". Please remove any half-completed changes then run repair to fix the schema history.";
:
创建错误消息,指示检测到失败的迁移至版本号,提示移除任何未完成的更改,然后运行修复以修复模式历史记录。
- return new ErrorDetails(ErrorCode.FAILED_VERSIONED_MIGRATION, errorMessage);
:
返回一个包含错误代码和错误消息的 ErrorDetails
对象,表示检测到失败的有版本号的迁移。 通过这段代码,Flyway在发现迁移失败时,根据迁移是否有版本号来生成相应的错误消息并返回适当的错误代码,以指导用户如何处理和修复失败的迁移。
7.2 Flyway检测已应用的迁移但未在本地解析的情况,根据迁移是否有版本号生成相应的错误消息并返回适当的错误代码,以提示用户如何处理这种情况
if ((resolvedMigration == null)&& !appliedMigration.getType().isSynthetic()&& (!context.missing || (MigrationState.MISSING_SUCCESS != state && MigrationState.MISSING_FAILED != state))&& (!context.future || (MigrationState.FUTURE_SUCCESS != state && MigrationState.FUTURE_FAILED != state))) {if (appliedMigration.getVersion() != null) {String errorMessage = "Detected applied migration not resolved locally: " + getVersion() + ". If you removed this migration intentionally, run repair to mark the migration as deleted.";return new ErrorDetails(ErrorCode.APPLIED_VERSIONED_MIGRATION_NOT_RESOLVED, errorMessage);} else {String errorMessage = "Detected applied migration not resolved locally: " + getDescription() + ". If you removed this migration intentionally, run repair to mark the migration as deleted.";return new ErrorDetails(ErrorCode.APPLIED_REPEATABLE_MIGRATION_NOT_RESOLVED, errorMessage);} }
1. if ((resolvedMigration == null) && !appliedMigration.getType().isSynthetic() && (!context.missing || (MigrationState.MISSING_SUCCESS != state && MigrationState.MISSING_FAILED != state)) && (!context.future || (MigrationState.FUTURE_SUCCESS != state && MigrationState.FUTURE_FAILED != state))) {
:
如果未解析的迁移为 null
,且应用迁移类型不是合成的,且不是缺失状态或未来状态成功或失败,执行以下逻辑。
2. 如果已应用迁移有版本号,则执行以下逻辑:
- String errorMessage = "Detected applied migration not resolved locally: " + getVersion() + ". If you removed this migration intentionally, run repair to mark the migration as deleted.";
:
创建错误消息,指示检测到已应用的迁移未在本地解析,提示如果有意删除此迁移,则运行修复以标记迁移为已删除。
- return new ErrorDetails(ErrorCode.APPLIED_VERSIONED_MIGRATION_NOT_RESOLVED, errorMessage);
:
返回一个包含错误代码和错误消息的 ErrorDetails
对象,表示检测到已应用的有版本号的迁移未在本地解析。 3. 如果已应用迁移没有版本号,则执行以下逻辑:
- String errorMessage = "Detected applied migration not resolved locally: " + getDescription() + ". If you removed this migration intentionally, run repair to mark the migration as deleted.";
:
创建错误消息,指示检测到已应用的迁移未在本地解析,提示如果有意删除此迁移,则运行修复以标记迁移为已删除。
- return new ErrorDetails(ErrorCode.APPLIED_REPEATABLE_MIGRATION_NOT_RESOLVED, errorMessage);
:返回一个包含错误代码和错误消息的 ErrorDetails
对象,表示检测到已应用的可重复迁移未在本地解析。
通过这段代码,Flyway检测已应用的迁移但未在本地解析的情况,根据迁移是否有版本号生成相应的错误消息并返回适当的错误代码,以提示用户如何处理这种情况。
7.3 Flyway检测已解析但被忽略的迁移情况,根据迁移是否有版本号生成相应的错误消息并返回适当的错误代码,以提示用户如何处理这种情况。
if (!context.ignored && MigrationState.IGNORED == state) {if (getVersion() != null) {String errorMessage = "Detected resolved migration not applied to database: " + getVersion() + ". To ignore this migration, set -ignoreIgnoredMigrations=true. To allow executing this migration, set -outOfOrder=true.";return new ErrorDetails(ErrorCode.RESOLVED_VERSIONED_MIGRATION_NOT_APPLIED, errorMessage);}String errorMessage = "Detected resolved repeatable migration not applied to database: " + getDescription() + ". To ignore this migration, set -ignoreIgnoredMigrations=true.";return new ErrorDetails(ErrorCode.RESOLVED_REPEATABLE_MIGRATION_NOT_APPLIED, errorMessage); }
1. if (!context.ignored && MigrationState.IGNORED == state) {
:
如果未设置忽略标志且迁移状态为被忽略,则执行以下逻辑。
2. 如果已解析的迁移有版本号,则执行以下逻辑:
- String errorMessage = "Detected resolved migration not applied to database: " + getVersion() + ". To ignore this migration, set -ignoreIgnoredMigrations=true. To allow executing this migration, set -outOfOrder=true.";
:
创建错误消息,指示检测到已解析但未应用到数据库的迁移,提示如果要忽略此迁移,则设置 -ignoreIgnoredMigrations=true
。要允许执行此迁移,设置
-outOfOrder=true
。
- return new ErrorDetails(ErrorCode.RESOLVED_VERSIONED_MIGRATION_NOT_APPLIED, errorMessage);
:
返回一个包含错误代码和错误消息的 ErrorDetails
对象,表示检测到已解析但未应用的有版本号的迁移。
3. 如果已解析的迁移没有版本号,则执行以下逻辑:
- String errorMessage = "Detected resolved repeatable migration not applied to database: " + getDescription() + ". To ignore this migration, set -ignoreIgnoredMigrations=true.";
:
创建错误消息,指示检测到已解析但未应用到数据库的可重复迁移,提示如果要忽略此迁移,则设置
-ignoreIgnoredMigrations=true
。 - return new ErrorDetails(ErrorCode.RESOLVED_REPEATABLE_MIGRATION_NOT_APPLIED, errorMessage);
:返回一个包含错误代码和错误消息的 ErrorDetails
对象,表示检测到已解析但未应用的可重复迁移。
通过这段代码,Flyway检测已解析但被忽略的迁移情况,根据迁移是否有版本号生成相应的错误消息并返回适当的错误代码,以提示用户如何处理这种情况。
7.4 通过这段代码,Flyway在发现已解析的迁移未应用到数据库中时,根据是否有版本号生成相应的错误消息并返回适当的错误代码,以提示用户如何解决这一问题。
if (!context.pending && MigrationState.PENDING == state) {if (getVersion() != null) {String errorMessage = "Detected resolved migration not applied to database: " + getVersion() + ". To fix this error, either run migrate, or set -ignorePendingMigrations=true.";return new ErrorDetails(ErrorCode.RESOLVED_VERSIONED_MIGRATION_NOT_APPLIED, errorMessage);}String errorMessage = "Detected resolved repeatable migration not applied to database: " + getDescription() + ". To fix this error, either run migrate, or set -ignorePendingMigrations=true.";return new ErrorDetails(ErrorCode.RESOLVED_REPEATABLE_MIGRATION_NOT_APPLIED, errorMessage); }
1. if (!context.pending && MigrationState.PENDING == state) {
:
如果不是待处理状态且迁移状态为待处理,则执行以下逻辑。
2. 如果当前迁移有版本号,则执行以下逻辑:
- String errorMessage = "Detected resolved migration not applied to database: " + getVersion() + ". To fix this error, either run migrate, or set -ignorePendingMigrations=true.";
:
创建错误消息,指示检测到已解析的迁移未应用到数据库中,提示要解决此错误,需运行 migrate
命令或设置 -ignorePendingMigrations=true
选项。
- return new ErrorDetails(ErrorCode.RESOLVED_VERSIONED_MIGRATION_NOT_APPLIED, errorMessage);
:
返回一个包含错误代码和错误消息的 ErrorDetails
对象,表示检测到已解析的有版本号的迁移未应用到数据库中。
3. 如果当前迁移没有版本号,则执行以下逻辑:
- String errorMessage = "Detected resolved repeatable migration not applied to database: " + getDescription() + ". To fix this error, either run migrate, or set -ignorePendingMigrations=true.";
:
创建错误消息,指示检测到已解析的可重复迁移未应用到数据库中,提示要解决此错误,需运行 migrate
命令或设置 -ignorePendingMigrations=true
选项。
- return new ErrorDetails(ErrorCode.RESOLVED_REPEATABLE_MIGRATION_NOT_APPLIED, errorMessage);
:
返回一个包含错误代码和错误消息的 ErrorDetails
对象,表示检测到已解析的可重复迁移未应用到数据库中。
通过这段代码,Flyway在发现已解析的迁移未应用到数据库中时,根据是否有版本号生成相应的错误消息并返回适当的错误代码,以提示用户如何解决这一问题。
7.5
if (!context.pending && MigrationState.OUTDATED == state) {String errorMessage = "Detected outdated resolved repeatable migration that should be re-applied to database: " + getDescription() + ". Run migrate to execute this migration.";return new ErrorDetails(ErrorCode.OUTDATED_REPEATABLE_MIGRATION, errorMessage); }
if (resolvedMigration != null && appliedMigration != null&& getType() != MigrationType.DELETE ) {String migrationIdentifier = appliedMigration.getVersion() == null ?// Repeatable migrationsappliedMigration.getScript() :// Versioned migrations"version " + appliedMigration.getVersion();if (getVersion() == null || getVersion().compareTo(context.baseline) > 0) {if (resolvedMigration.getType() != appliedMigration.getType()) {String mismatchMessage = createMismatchMessage("type", migrationIdentifier,appliedMigration.getType(), resolvedMigration.getType());return new ErrorDetails(ErrorCode.TYPE_MISMATCH, mismatchMessage);}if (resolvedMigration.getVersion() != null|| (context.pending && MigrationState.OUTDATED != state && MigrationState.SUPERSEDED != state)) {if (!resolvedMigration.checksumMatches(appliedMigration.getChecksum())) {String mismatchMessage = createMismatchMessage("checksum", migrationIdentifier,appliedMigration.getChecksum(), resolvedMigration.getChecksum());return new ErrorDetails(ErrorCode.CHECKSUM_MISMATCH, mismatchMessage);}}if (descriptionMismatch(resolvedMigration, appliedMigration)) {String mismatchMessage = createMismatchMessage("description", migrationIdentifier,appliedMigration.getDescription(), resolvedMigration.getDescription());return new ErrorDetails(ErrorCode.DESCRIPTION_MISMATCH, mismatchMessage);}} }