Flyway主要源码解析(基于注释)

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

  1. 如果迁移历史表不存在,就创建。
  2. 根据configuration.isGroup()属性,将迁移打包到一个事务内进行迁移。由于mysql不支持ddl事务,一般不使用这种方式。
  3. 迁移前后会有基于迁移前后的事件处理。
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

用途

  1. 删除失败的迁移条目

  2. 将db中已应用的校验和、描述和类型更新为类路径中可用迁移的校验和、描述和类型

流程

  1. 删除失败的记录
  2. 刷新上下文与迁移信息
  3. 进行修复
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下的迁移信息,维护几个重要变量

  1. context.lastResolved,MigrationVersion类型,类路径下最新的版本(V)
  2. context.lastApplied,MigrationVersion类型,DB最新应用的V类型版本
  3. context.baseline,MigrationVersion类型,迁移基准
  4. context.latestRepeatableRuns,db中R类型最新的rank(类比lastApplied)
  5. 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;
}
阅读终点,创作起航,您可以撰写心得或摘录文章要点写篇博文。去创作
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

丿初学者的心态

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值