Validate failed: Migrations have failed validation

启动项目时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将已解析的迁移根据是否有版本号进行分类,分别存储到 resolvedVersionedresolvedRepeatable 映射中,以便后续执行数据库迁移操作时能够区分处理有版本号和可重复的迁移。

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) {

这行代码检查目标版本是否已指定且不等于 CURRENTLATEST 。这表示要求目标版本不是当前版本或最新版本,而是一个具体的版本号。

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);}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/789406.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

信创(统信)系统上的软件安装及软件使用手册

一.各软件的安装文档 1.达梦数据库在统信系统上的安装 官方手册:https://eco.dameng.com/document/dm/zh-cn/start/install-dm-linux-prepare.html 1.1下载安装包 官网:https://www.dameng.com/list_103.html 点击”服务与合作”--> “下载中心” 这里选择对应的cpu和操作…

Git 核心知识

2024年4月2日更新 Git 安装 官网下载&#xff0c;Git 选择合适的版本&#xff0c;无脑下一步即可。 安装成功之后&#xff0c;鼠标右键任意的文件夹&#xff0c;会出现 Git GUI 的选项&#xff0c;即安装成功 安装注意事项 安装前&#xff0c;检查环境变量 &#xff0c; 如…

程序的编译、链接过程分析(简洁浓缩版)!

《嵌入式工程师自我修养/C语言》系列——程序的编译、链接过程分析&#xff08;简洁浓缩版&#xff09;&#xff01; 一、程序的编译1.1 预编译指令 pragma1.2 编译过程概述1.3 符号表和重定位表 二、程序的链接2.1 分段组装2.2 符号决议2.2.1 强符号与弱符号2.2.2 GNU编译器的…

在线考试系统的设计与实现|Springboot+ Mysql+Java+ B/S结构(可运行源码+数据库+设计文档)高校远程网络考试,课程信息,在线考试,试题管理,习题管理课程信息,教师管理,学生管理

本项目包含可运行源码数据库LW&#xff0c;文末可获取本项目的所有资料。 推荐阅读300套最新项目持续更新中..... 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 2024年56套包含ja…

C库函数详解(二)

3.输入输出函数 凡用以下的输入输出函数,应该使用#include把stdio.h头文件包含到源程序文件中。 函数名 函数原型 功 能 返回值 包含文件 clearerr void clearerr(FILE*fp); 使fp所指文件的错误,标志和文件结束标志置0。 无 close int close (int fp): 关闭文件。…

切换IP地址的软件的基本原理及相关知识

在数字化时代&#xff0c;IP地址作为网络设备的唯一标识&#xff0c;扮演着至关重要的角色。然而&#xff0c;在某些特定情况下&#xff0c;我们可能需要隐藏或更改自己的IP地址&#xff0c;以保护隐私、突破地域限制或进行网络测试等。这时&#xff0c;切换IP地址的软件就显得…

Harmony创建Page省事小技巧

在创建Page页面时&#xff0c;选择ArkTS File时&#xff0c;创建的文件不会自动生成基础代码&#xff0c;也不会自动在main_page.json中自动进行注册&#xff0c;如何解决问题呢&#xff0c;其实很简单创建Page页面时选择Page项后就会创建Page文件&#xff0c;创建完的页面会自…

放弃“颜值”,15年的美图开始拼“生产力”

文&#xff5c;郝 鑫 “美图是一家AI公司”&#xff0c;美图在2023年财报的一开头就迫不及待地重申了自己的“新身份”。 这背后源于美图去年到现在对AIGC的一连串疯狂押注。 2023年6月&#xff0c;美图一口气发布了美图视觉大模型MiracleVision和六款AI产品&#xff0c;…

【bug解决】C++调用python库不能重复调用的问题解决

项目场景&#xff1a; 使用C调用Python环境中的代码&#xff0c;其中Python的代码已经封装在site-pages目录下。 问题描述 在使用C进行调用时&#xff0c;在C程序的运行过程中&#xff0c;只能调用一次Python代码&#xff0c;反复调用就会失败。 原因分析&#xff1a; 在py…

MCFrame中设置了按钮,但标签文字不能正常显示为设置的文字

可能是id属性出现了问题&#xff0c;注意id与name是一个类似的东西&#xff0c;都是唯一标识

护眼灯值不值得买?业界公认的护眼台灯十大排名盘点

在这个信息爆炸的时代&#xff0c;用眼时间不断延长&#xff0c;长时间用眼不仅易导致视觉疲劳&#xff0c;还可能对视力健康造成长远影响。因此&#xff0c;除了适时休息&#xff0c;确保所处光线环境健康同样重要。然而&#xff0c;灯光环境往往不尽如人意。此时&#xff0c;…

状态压缩DP

哈密顿路径问题&#xff1a; 一般设 表示 状态下&#xff0c;为最后一个最值情况 。 一般有两种稍微不同的写法&#xff0c;单纯就是写法不同&#xff0c;思路方法都相同。 第一个例题为第一种转移方法&#xff0c;有当前转移后面。 后面的都是由前面转移目前。 G. Shuff…

js中this指向以及改变this指向方法

js中this指向以及改变this指向方法 this指向 1、在构造函数中this指向实例化对象 2、在自定义对象的函数&#xff08;方法&#xff09;中this指向当前的对象 3、在普通函数中this指向window 4、在事件函数中this指向事件源 5、在定时器函数中this指向window 6、原型对象…

是谁?写的Java神作一出版就获Jolt图书大奖【抽奖赠书】

送书活动 1️⃣参与方式&#xff1a;点此参与抽书抽奖 2️⃣获奖方式&#xff1a;小程序随机 抽5位&#xff0c;每位小伙伴一本《Effective Java中文2024版》 3️⃣活动时间&#xff1a;截止到 2024-4-6 12:01:00 注&#xff1a;所有抽奖活动都是全国范围免费包邮到家&#xff…

Unity类银河恶魔城学习记录11-15 p117 Ice and Fire item Effect源代码

Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释&#xff0c;可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili IceAndFire_Controller.cs using System.Collections; using System.Coll…

MP4文件中h264的 SPS、PPS获取

MP4文件中h264的SPS、PPS获取 如下图所示&#xff0c;为avcC 1 【参考依据】ISO/IEC 14496-15 2 【综述】在H264中&#xff0c;SPS和PPS存在于NALU header中&#xff0c;而在MP4文件中&#xff0c;SPS和PPS存在于AVCDecoderConfigurationRecord&#xff0c; 首先要定位avcC. …

Ant Design Vue中的table与pagination的联合使用

效果&#xff1a; 代码&#xff1a; <a-table:dataSource"dataSource":columns"columns":pagination"pagination"change"handleTableChange":scroll"{ x: 100%, y: 600 }"> </a-table> export default defin…

基于springboot实现定时任务,并且添加Event事件处理机制

1、基于Spring-Event增加事件处理机制 import org.bson.Document; import org.springframework.context.ApplicationEvent;/*** 基于Spring-Event增加事件处理机制* create: 2024/4/1-13:33*/ public class SysProductConfigEvent extends ApplicationEvent {// 数据配置priv…

MetaGPT教程学习笔记

官方文档&#xff1a; MetaGPT: 多智能体框架 | MetaGPT 学习手册及教程&#xff1a; Docs Docs 第一章 前期准备 安装提供了两种方式&#xff0c;pip和github安装包。 配置也提供了两种方式&#xff0c;执行命令或在config.yaml中修改&#xff0c;需提前获取API key。 …

kubernetes-Pod基于污点、容忍度、亲和性的多种调度策略(二)

Pod调度策略 一.污点-Taint二.容忍度-Tolerations三.Pod常见状态和重启策略1.Pod常见状态2.Pod的重启策略2.1测试Always重启策略2.2测试Never重启策略2.3测试OnFailure重启策略&#xff08;生产环境中常用&#xff09; 一.污点-Taint 在 Kubernetes 中&#xff0c;污点&#x…