Как перейти с flyway 3 прямо на flyway 5

Работа над продуктом, который развертывается многими клиентами во многих производственных средах. Включает в себя как минимум одно приложение Spring Boot.

Мы использовали flyway для миграции схемы БД. Обновление Spring Boot с версии 1.5.x до версии 2.0.x увеличило нашу версию Flyway с версии 3.x до 5.x.

В руководстве по миграции Spring Boot просто говорится о том, чтобы выполнить обновление до flyway 4 до обновления загрузки. Однако для этого всем нашим клиентам потребуется выполнить промежуточное обновление, прежде чем оно сможет обновиться до последней версии.

Итак, вопрос: как бы вы перешли с пролетного пути 3 прямо на пролетный путь 5?

3 ответа

Решение

В случае, если я не последний человек на планете, который все еще будет повышаться с 3 до 5.

Проблема:

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

Я посмотрел, как версия 4 справилась с обновлением:

  • В Flyway.java выполняется вызов MetaDataTableImpl.upgradeIfNeeded
  • upgradeIf Необходимо проверяет, существует ли еще столбец version_rank, и в этом случае запускает сценарий миграции с именем upgradeMetaDataTable.sql из org/flywaydb/core/internal/dbsupport/YOUR_DB/
  • Если обновление выполнено, если необходимо, Flyway.java запускает DbRepair с вызовом repairChecksumsAndDescription.

Это достаточно просто сделать вручную, но сделать его прозрачным. Приложение является весенним приложением, но не весенним загрузочным приложением, поэтому в то время, когда у меня был flyway, выполняющий миграции автоматически при запуске приложения, поскольку конструкция bean-компонента LocalContainerEntityManager зависела от bean-компонента flyway, который вызывал migrate в качестве его метода init (объясняется здесь Flyway Интеграция Spring JPA2 - возможно ли сохранить проверку схемы?), Поэтому порядок начальной загрузки будет:

Flyway bean created -> Flyway migrate called -> LocalContainerEntityManager created

Решение:

Я изменил порядок начальной загрузки на:

Flyway bean created -> Flyway3To4Migrator -> LocalContainerEntityManager created

где Flyway3To4Migrator будет выполнять изменения schema_table, если это необходимо, запускать восстановление, если произойдет обновление, а затем всегда запускать flyway.migrate, чтобы продолжить миграцию.

@Configuration
public class AppConfiguration {

    @Bean
    // Previously: @DependsOn("flyway")
    @DependsOn("flyway3To4Migrator")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
        ...
    }

    // Previously: @Bean(initMethod = "migrate")
    @Bean
    public Flyway flyway(DataSource dataSource) {
        ...
    }
}

@Component
@DependsOn("flyway")
public class Flyway3To4Migrator {
    private final Log logger = LogFactory.getLog(getClass());
    private Flyway flyway;

    @Autowired
    public Flyway3To4Migrator(Flyway flyway) {
        this.flyway = flyway;
    }

    @PostConstruct
    public void migrate() throws SQLException, MetaDataAccessException {
        DataSource dataSource = flyway.getDataSource();

        boolean versionRankColumnExists = checkColumnExists(dataSource);
        if (versionRankColumnExists) {
            logger.info("Upgrading metadata table to the Flyway 4.0 format ...");
            Resource resource = new ClassPathResource("upgradeMetaDataTable.sql", getClass().getClassLoader());
            ScriptUtils.executeSqlScript(dataSource.getConnection(), resource);
            logger.info("Metadata table successfully upgraded to the Flyway 4.0 format.");

            logger.info("Running flyway:repair for Flyway upgrade.");
            flyway.repair();
            logger.info("Complete flyway:repair.");
        }

        logger.info("Continuing with normal Flyway migration.");
        flyway.migrate();
    }

    private boolean checkColumnExists(DataSource dataSource) throws MetaDataAccessException {
        return (Boolean) JdbcUtils.extractDatabaseMetaData(
            dataSource, dbmd -> {
                ResultSet rs = dbmd.getColumns(
                        null, null,
                        "schema_version",
                        "version_rank");
                return rs.next();
            });
    }
}

Несколько вещей, на которые стоит обратить внимание:

  • В какой-то момент мы удалим лишний класс Flyway3To4Migrator и вернем конфигурацию в прежнее состояние.
  • Я скопировал соответствующий файл upgradeMetaDataTable.sql для своей базы данных из фляги v4 Flyway и упростил его до имен таблиц и т. Д. При необходимости вы можете получить схемы и имена таблиц с flyway.
  • нет никакого управления транзакциями вокруг сценария SQL, вы можете добавить это
  • Flyway3To4Migrator вызывает flyway.repair(), что немного больше, чем DbRepair.repairChecksumsAndDescription (), но мы были рады принять, что база данных должна быть в хорошем состоянии до ее запуска

Шаг 0

Обновление до весенней загрузки v2.1 (и тем самым неявно до пролетного 5).

Шаг 1.

поскольку schema_version был использован в flyway 3.x, чтобы новые версии flyway знали, что они должны продолжать использовать эту таблицу.:

# application.yml
spring.flyway.table: schema_version # prior flyway version used this table and we keep it

Шаг 2.

Создать файл src/main/ressources/db/migration/flyway_upgradeMetaDataTable_V3_to_V4.sql для обновления мета-таблицы на основе используемого вами диалекта.

См. https://github.com/flyway/flyway/commit/cea8526d7d0a9b0ec35bffa5cb43ae08ea5849e4 для сценариев обновления нескольких диалектов.

Вот тот, что для postgres, и предполагается, что имя таблицы flyway schema_version:

-- src/main/ressources/db/migration/flyway_upgradeMetaDataTable_V3_to_V4.sql
DROP INDEX "schema_version_vr_idx";
DROP INDEX "schema_version_ir_idx";
ALTER TABLE "schema_version" DROP COLUMN "version_rank";
ALTER TABLE "schema_version" DROP CONSTRAINT "schema_version_pk";
ALTER TABLE "schema_version" ALTER COLUMN "version" DROP NOT NULL;
ALTER TABLE "schema_version" ADD CONSTRAINT "schema_version_pk" PRIMARY KEY ("installed_rank");
UPDATE "schema_version" SET "type"='BASELINE' WHERE "type"='INIT';

Шаг 3.

Создать файл Java your.package/FlywayUpdate3To4Callback.java

Обратите внимание, что это делает следующее:

  • Запустите сценарий sql из шага 2
  • вызов Flyway.repair()
// FlywayUpdate3To4Callback.java
package your.package;

import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE;

import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.callback.Callback;
import org.flywaydb.core.api.callback.Context;
import org.flywaydb.core.api.callback.Event;
import org.flywaydb.core.api.configuration.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.init.ScriptUtils;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.jdbc.support.MetaDataAccessException;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

@Component
@Order(HIGHEST_PRECEDENCE)
@Slf4j
public class FlywayUpdate3To4Callback implements Callback {
    private final Flyway flyway;

    public FlywayUpdate3To4Callback(@Lazy Flyway flyway) {
        this.flyway = flyway;
    }

    private boolean checkColumnExists(Configuration flywayConfiguration) throws MetaDataAccessException {
        return (boolean) JdbcUtils.extractDatabaseMetaData(flywayConfiguration.getDataSource(),
                callback -> callback
                        .getColumns(null, null, flywayConfiguration.getTable(), "version_rank")
                        .next());
    }

    @Override
    public boolean supports(Event event, Context context) {
        return event == Event.BEFORE_VALIDATE;
    }

    @Override
    public boolean canHandleInTransaction(Event event, Context context) {
        return false;
    }

    @Override
    public void handle(Event event, Context context) {
        boolean versionRankColumnExists = false;
        try {
            versionRankColumnExists = checkColumnExists(context.getConfiguration());
        } catch (MetaDataAccessException e) {
            log.error("Cannot obtain flyway metadata");
            return;
        }
        if (versionRankColumnExists) {
            log.info("Upgrading metadata table the Flyway 4.0 format ...");
            Resource resource = new ClassPathResource("db/migration/common/flyway_upgradeMetaDataTable_V3_to_V4.sql",
                    Thread.currentThread().getContextClassLoader());
            ScriptUtils.executeSqlScript(context.getConnection(), resource);
            log.info("Flyway metadata table updated successfully.");
            // recalculate checksums
            flyway.repair();
        }
    }
}

Шаг 4

Запустите пружинный ботинок.

В журнале должны отображаться информационные сообщения, подобные этим:

...FlywayUpdate3To4Callback      : Upgrading metadata table the Flyway 4.0 format 
...FlywayUpdate3To4Callback      : Flyway metadata table updated successfully.

кредиты

Этот ответ основан на ответе Эдуардо Родригеса путем изменения:

  • использование Event.BEFORE_VALIDATE вызвать обратный вызов, который улучшит пролет 3 до 4.
  • больше информации о настройке application.yml
  • обеспечить обновление сценария миграции sql

Если вы используете Spring Boot, вы можете зарегистрировать обратный вызов, который выполняет обновление на beforeMigrate(). Код похож на @trf и выглядит так:

@Component
@Order(HIGHEST_PRECEDENCE)
@Slf4j
public class FlywayUpdate3To4Callback extends BaseFlywayCallback {
    private final Flyway flyway;

    public FlywayUpdate3To4Callback(@Lazy Flyway flyway) {
        this.flyway = flyway;
    }

    @Override
    public void beforeMigrate(Connection connection) {
        boolean versionRankColumnExists = false;
        try {
            versionRankColumnExists = checkColumnExists(flywayConfiguration);
        } catch (MetaDataAccessException e) {
            log.error("Cannot obtain flyway metadata");
            return;
        }
        if (versionRankColumnExists) {
            log.info("Upgrading metadata table the Flyway 4.0 format ...");
            Resource resource = new ClassPathResource("upgradeMetaDataTable.sql",
                    Thread.currentThread().getContextClassLoader());
            ScriptUtils.executeSqlScript(connection, resource);
            log.info("Flyway metadata table updated successfully.");
            // recalculate checksums
            flyway.repair();
        }
    }

    private boolean checkColumnExists(FlywayConfiguration flywayConfiguration) throws MetaDataAccessException {
        return (boolean) JdbcUtils.extractDatabaseMetaData(flywayConfiguration.getDataSource(),
                callback -> callback
                        .getColumns(null, null, flywayConfiguration.getTable(), "version_rank")
                        .next());
    }

Обратите внимание, что вам не нужно вручную вызывать flyway.migrate() здесь.

Приведенный выше код не совместим с версией 5. Он использует устаревшие классы. Вот обновленная версия.

import lombok.extern.slf4j.Slf4j;
import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.callback.Callback;
import org.flywaydb.core.api.callback.Context;
import org.flywaydb.core.api.callback.Event;
import org.flywaydb.core.api.configuration.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.init.ScriptUtils;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.jdbc.support.MetaDataAccessException;
import org.springframework.stereotype.Component;

import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE;

@Component
@Order(HIGHEST_PRECEDENCE)
@Slf4j
public class FlywayUpdate3To4Callback implements Callback {
    private final Flyway flyway;

    public FlywayUpdate3To4Callback(@Lazy Flyway flyway) {
        this.flyway = flyway;
    }


    private boolean checkColumnExists(Configuration flywayConfiguration) throws MetaDataAccessException {
        return (boolean) JdbcUtils.extractDatabaseMetaData(flywayConfiguration.getDataSource(),
                callback -> callback
                        .getColumns(null, null, flywayConfiguration.getTable(), "version_rank")
                        .next());
    }

    @Override
    public boolean supports(Event event, Context context) {
        return event == Event.BEFORE_MIGRATE;
    }

    @Override
    public boolean canHandleInTransaction(Event event, Context context) {
        return false;
    }

    @Override
    public void handle(Event event, Context context) {
        boolean versionRankColumnExists = false;
        try {
            versionRankColumnExists = checkColumnExists(context.getConfiguration());
        } catch (MetaDataAccessException e) {
            log.error("Cannot obtain flyway metadata");
            return;
        }
        if (versionRankColumnExists) {
            log.info("Upgrading metadata table the Flyway 4.0 format ...");
            Resource resource = new ClassPathResource("flyway_upgradeMetaDataTable_V3_to_V4.sql",
                    Thread.currentThread().getContextClassLoader());
            ScriptUtils.executeSqlScript(context.getConnection(), resource);
            log.info("Flyway metadata table updated successfully.");
            // recalculate checksums
            flyway.repair();
        }
    }
}

Я тоже пытался пропустить v4, но ничего не вышло. Выполнение ремонта с 3 по 5 исправит контрольные суммы, но не изменит schema_version формат. Это тоже изменилось.

Кажется, вам нужно сначала перейти на v4. Даже если временно просто запустить mvn flyway:validate, который будет ремонтировать schema_version,

Я сделал это в этом репо: https://github.com/fabiofalci/flyway-from-3-to-5/commits/5.0.7

Первый коммит v3, второй коммит v4 (где я запустил валидацию), а затем третий коммит на v5 схема верна.

У меня это сработало, за исключением того, что мне пришлось снова поставить Event.BEFORE_VALIDATE вместо Event.BEFORE_MIGRATE, который присутствовал в последней версии класса FlywayUpdate3To4Callback. Это связано с тем, что у меня была недействительная контрольная сумма при уже запущенной миграции, поэтому ее нужно было исправить перед проверкой, а не перед миграцией. Спасибо.

Другие вопросы по тегам