Как перейти с 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. Это связано с тем, что у меня была недействительная контрольная сумма при уже запущенной миграции, поэтому ее нужно было исправить перед проверкой, а не перед миграцией. Спасибо.