flyway: общий сценарий, запускаемый после каждой миграции

У меня есть общий сценарий очистки, который я бы хотел запускать после каждой миграции. Есть ли хороший способ запускать этот сценарий после каждой миграции (за исключением включения самого сценария в качестве изменения каждый раз, когда я выполняю миграцию?)

Я вижу, что этот вопрос задавался раньше здесь Скрипты до и после миграции для Проезд, и тогда ответ был отрицательным.

Изменился ли вообще ответ за последние 1,5 года?


person user2009267    schedule 05.02.2013    source источник
comment
На самом деле это не ответ, но своего рода обходной путь: я решил аналогичную проблему, просто удалив запись для такого сценария из таблицы SCHEMA_VERSION перед началом миграции с пролетного пути. Если входа нет, flyway снова применит сценарий.   -  person Stefan Ferstl    schedule 06.02.2013
comment
хех. если не придет другой ответ, я буду иметь это в виду. Благодарю.   -  person user2009267    schedule 06.02.2013


Ответы (4)


С flyway 3.0 ситуация изменилась, и теперь возможны сценарии обратного вызова. В этой ситуации для очистки можно использовать файл afterMigration.sql.

См. http://flywaydb.org/documentation/callbacks.html для получения дополнительной информации.

person Markus Heberling    schedule 16.06.2015

Это не изменилось. На данный момент воспользуйтесь любым из предложенных обходных путей.

person Axel Fontaine    schedule 06.02.2013

Я просмотрел предложения здесь и сценарии до и после миграции для Flyway и хотел бы указать на случай, когда я не вижу, какой обходной путь (если таковой имеется) будет наиболее применимым. Пример использования - создать точку восстановления в dba перед запуском миграций, созданных разработчиком.

Прямо сейчас, с нашим процессом миграции вручную (без пролетного пути), dba создает точку восстановления перед запуском набора миграций. Миграция прошла бы нормально без точки восстановления. Но если у них нет правильного кода (например, отсутствует создание столбца), часто предпочтительнее откатиться к точке восстановления Oracle, чтобы избежать простоев и дать разработчику время для работы над исправлением.

Я не думаю, что имеет смысл требовать от разработчика включения миграции, которая выполняет эту точку восстановления, потому что: 1. Они могут забыть (это должно произойти автоматически, без вмешательства разработчика) 2. В зависимости от состояния схемы могут быть разные запуск миграции, поэтому, если тот, который включает точку восстановления, не запущен, он может быть старым, а данные за это время могли измениться.

Наличие отдельной миграции, которая делает точку восстановления, имеет аналогичные недостатки: 1. Им придется вручную создать новую миграцию, которая по сути является копией старой миграции с другим номером версии, чтобы сделать точку восстановления.

Для схем разработки с большими существующими данными нецелесообразно очищать схему во время разработки миграции, потому что она предшествует пролетному пути и может занять значительное время для воссоздания с нуля.

Для разработки в идеале рабочий процесс выглядит примерно так: 1. создать точку восстановления 2. разработать миграцию (-ы), запустить с помощью пролетного пути 3. выполнить откат до точки восстановления, если миграция не работает должным образом.

Если есть способ автоматизировать шаг № 1 по всем направлениям, это позволит нам использовать пролетный путь и исключить необходимость в dba, за исключением случаев, когда что-то пошло не так и потребуется откат. Возможно, существует более «пролетный» способ решения проблемы, но найденные мной обходные пути, похоже, не подходят для нашего существующего рабочего процесса.

person user1885771    schedule 07.02.2013

У нас была такая же проблема. То есть всегда вызывать кучу скриптов до и после каждой миграции. Например, удаление и создание материализованного представления, предоставление разрешений для таблиц. Эти сценарии не меняются от миграции к миграции, но их необходимо выполнить.

Поэтому я взял класс обратного вызова org.flywaydb.core.internal.callback.SqlScriptFlywayCallback и адаптировал его для нескольких файлов.

Я старался придерживаться философии flyway и использовать следующий шаблон. Файлы, начинающиеся с am__ или AM__, являются сценарием после переноса, файлы с bi__ - для информации до и т. Д. Я сортирую скрипты, чтобы они выполнялись в правильном порядке.

public class MultipleScriptPerCallback extends BaseFlywayCallback {
private static final Log LOG = LogFactory.getLog(SqlScriptFlywayCallback.class);
private static final String DELIMITER = "__";

private static final String BEFORE_CLEAN = "bc";
private static final String AFTER_CLEAN = "ac";
private static final String BEFORE_MIGRATE = "bm";
private static final String AFTER_MIGRATE = "am";
private static final String BEFORE_EACH_MIGRATE = "bem";
private static final String AFTER_EACH_MIGRATE = "aem";
private static final String BEFORE_VALIDATE = "bv";
private static final String AFTER_VALIDATE = "av";
private static final String BEFORE_BASELINE = "bb";
private static final String AFTER_BASELINE = "ab";
private static final String BEFORE_REPAIR = "br";
private static final String AFTER_REPAIR = "ar";
private static final String BEFORE_INFO = "bi";
private static final String AFTER_INFO = "ai";

private static final List<String> ALL_CALLBACKS = Arrays.asList(BEFORE_CLEAN, AFTER_CLEAN, BEFORE_MIGRATE, BEFORE_EACH_MIGRATE,
        AFTER_EACH_MIGRATE, AFTER_MIGRATE, BEFORE_VALIDATE, AFTER_VALIDATE, BEFORE_BASELINE, AFTER_BASELINE, BEFORE_REPAIR,
        AFTER_REPAIR, BEFORE_INFO, AFTER_INFO);

private Map<String, List<SqlScript>> scripts;

@Override
public void setFlywayConfiguration(FlywayConfiguration flywayConfiguration) {
    super.setFlywayConfiguration(flywayConfiguration);

    if (scripts == null) {
        scripts = registerScripts(flywayConfiguration);
    }
}

private Map<String, List<SqlScript>> registerScripts(FlywayConfiguration flywayConfiguration) {
    Map<String, List<SqlScript>> scripts = new HashMap<>();
    for (String callback : ALL_CALLBACKS) {
        scripts.put(callback, new ArrayList<SqlScript>());
    }

    LOG.debug(String.format("%s - Scanning for Multiple SQL callbacks ...", getClass().getSimpleName()));
    Locations locations = new Locations(flywayConfiguration.getLocations());
    Scanner scanner = new Scanner(flywayConfiguration.getClassLoader());
    String sqlMigrationSuffix = flywayConfiguration.getSqlMigrationSuffix();
    DbSupport dbSupport = dbSupport(flywayConfiguration);
    PlaceholderReplacer placeholderReplacer = createPlaceholderReplacer();
    String encoding = flywayConfiguration.getEncoding();

    for (Location location : locations.getLocations()) {
        Resource[] resources;
        try {
            resources = scanner.scanForResources(location, "", sqlMigrationSuffix);
        } catch (FlywayException e) {
            // Ignore missing locations
            continue;
        }
        for (Resource resource : resources) {
            String key = extractKeyFromFileName(resource);
            if (scripts.keySet().contains(key)) {
                LOG.debug(getClass().getSimpleName() + " - found script " + resource.getFilename() + " from location: " + location);
                List<SqlScript> sqlScripts = scripts.get(key);
                sqlScripts.add(new SqlScript(dbSupport, resource, placeholderReplacer, encoding));
            }
        }
    }

    LOG.info(getClass().getSimpleName() + " - scripts registered: " + prettyPrint(scripts));
    return scripts;
}

private String prettyPrint(Map<String, List<SqlScript>> scripts) {
    StringBuilder prettyPrint = new StringBuilder();
    boolean isFirst = true;
    for (String key : scripts.keySet()) {
        if (!isFirst) {
            prettyPrint.append("; ");
        }
        prettyPrint.append(key).append("=").append("[").append(prettyPrint(scripts.get(key))).append("]");
        isFirst = false;
    }
    return prettyPrint.toString();
}

private String prettyPrint(List<SqlScript> scripts) {
    StringBuilder prettyPrint = new StringBuilder();
    boolean isFirst = true;
    for (SqlScript script : scripts) {
        if (!isFirst) {
            prettyPrint.append(", ");
        }
        prettyPrint.append(script.getResource().getFilename());
        isFirst = false;
    }
    return prettyPrint.toString();
}

private String extractKeyFromFileName(Resource resource) {
    String filename = resource.getFilename();
    eturn filename.substring(0, (!filename.contains(DELIMITER)) ? 0 : filename.indexOf(DELIMITER)).toLowerCase();
}

private DbSupport dbSupport(FlywayConfiguration flywayConfiguration) {
    Connection connectionMetaDataTable = JdbcUtils.openConnection(flywayConfiguration.getDataSource());
    return DbSupportFactory.createDbSupport(connectionMetaDataTable, true);
}

/**
 * @return  A new, fully configured, PlaceholderReplacer.
 */
private PlaceholderReplacer createPlaceholderReplacer() {
    if (flywayConfiguration.isPlaceholderReplacement()) {
        return
            new PlaceholderReplacer(flywayConfiguration.getPlaceholders(), flywayConfiguration.getPlaceholderPrefix(),
                flywayConfiguration.getPlaceholderSuffix());
    }

    return PlaceholderReplacer.NO_PLACEHOLDERS;
}

@Override
public void beforeClean(Connection connection) {
    execute(BEFORE_CLEAN, connection);
}

@Override
public void afterClean(Connection connection) {
    execute(AFTER_CLEAN, connection);
}

@Override
public void beforeMigrate(Connection connection) {
    execute(BEFORE_MIGRATE, connection);
}

@Override
public void afterMigrate(Connection connection) {
    execute(AFTER_MIGRATE, connection);
}

@Override
public void beforeEachMigrate(Connection connection, MigrationInfo info) {
    execute(BEFORE_EACH_MIGRATE, connection);
}

@Override
public void afterEachMigrate(Connection connection, MigrationInfo info) {
    execute(AFTER_EACH_MIGRATE, connection);
}

@Override
public void beforeValidate(Connection connection) {
    execute(BEFORE_VALIDATE, connection);
}

@Override
public void afterValidate(Connection connection) {
    execute(AFTER_VALIDATE, connection);
}

@Override
public void beforeBaseline(Connection connection) {
    execute(BEFORE_BASELINE, connection);
}

@Override
public void afterBaseline(Connection connection) {
    execute(AFTER_BASELINE, connection);
}

@Override
public void beforeRepair(Connection connection) {
    execute(BEFORE_REPAIR, connection);
}

@Override
public void afterRepair(Connection connection) {
    execute(AFTER_REPAIR, connection);
}

@Override
public void beforeInfo(Connection connection) {
    execute(BEFORE_INFO, connection);
}

@Override
public void afterInfo(Connection connection) {
    execute(AFTER_INFO, connection);
}

private void execute(String key, Connection connection) {
    List<SqlScript> sqlScripts = scripts.get(key);
    LOG.debug(String.format("%s - sqlscript: %s for key: %s", getClass().getSimpleName(), sqlScripts, key));
    Collections.sort(sqlScripts, new SqlScriptLexicalComparator());
    for (SqlScript script : sqlScripts) {
        executeScript(key, connection, script);
    }
}

//Not private for testing
void executeScript(String key, Connection connection, SqlScript script) {
    LOG.info(String.format("%s - Executing SQL callback: %s : %s", getClass().getSimpleName(), key,
            script.getResource().getFilename()));
    script.execute(new JdbcTemplate(connection, 0));
}

//Not private for testing
static final class SqlScriptLexicalComparator implements Comparator<SqlScript> {
    @Override
    public int compare(SqlScript o1, SqlScript o2) {
        return Collator.getInstance().compare(o1.getResource().getFilename(), o2.getResource().getFilename());
    }
}

}

person fan    schedule 31.05.2016