Flyway 数据库脚本版本控制工具

Flyway简介

Flyway是一个简单开源数据库版本控制器(约定大于配置),主要提供migrate、clean、info、validate、baseline、repair等命令。它支持SQL(PL/SQL、T-SQL)方式和Java方式,支持命令行客户端等,还提供一系列的插件支持(Maven、Gradle、SBT、ANT等)。

官方网站:https://flywaydb.org/

 

概念

版本:对数据库的每一次变更可称为一个版本。

迁移:Flyway把数据库结构从一个版本更新到另一个版本叫做迁移。

可用的迁移:Flyway的文件系统识别出来的迁移版本。

已经应用的迁移:Flyway已经对数据库执行过的迁移。

 

flyway基本命令

Migrate:应用所有的迁移到最新版本,它会在你的DB中新建个表schema_version来存放每次升级的版本信息。

Clean:clean all objects

Info:打印所有的迁移的信息以及状态。

Validate:迁移之前进行验证。

Baseline:初始化schema_version表,并插入一条原始verion=1。

Repair:它主要做了两件事,移除所有失败的迁移(升级),重置校验和。

 

SQL脚本的命名规则

  • 前缀V用于版本化(可配置), U用于撤消(可配置)和 R用于可重复迁移(可配置)
  • 版本:带点或下划线的版本可以根据需要分开多个部分(不适用于可重复的迁移)
  • 分隔符 :( __两个下划线)(可配置)
  • 描述:下划线或空格分隔单词
  • 后缀.sql(配置)

   Flyway 数据库脚本版本控制工具_第1张图片

 SQL存放路径规则

    Flyway 数据库脚本版本控制工具_第2张图片

SQL句法

Flyway支持所有常规SQL语法元素,包括:

  • 单行或多行语句
  • 单( - )或多行(/ * * /)注释跨越完整的行
  • 特定于数据库的SQL语法扩展(PL / SQL,T-SQL,...)通常用于定义存储过程,包,...

此外,对于Oracle,Flyway还支持SQL * Plus命令。

 

命令-迁移

Migrate(迁移)是Flyway工作流程的核心。它将扫描文件系统或类路径以获取可用的迁移。然后将它们与已应用于数据库的迁移进行比较。如果发现任何差异,它将迁移数据库以缩小差距。迁移最好应在应用程序启动时执行,以避免数据库与代码期望之间的任何不兼容性。

 Flyway 数据库脚本版本控制工具_第3张图片

 执行migrate是幂等的,无论当前版本的架构如何,都可以安全地执行。

示例1:我们的迁移可用版本9,数据库版本为5。
迁移将按顺序应用迁移6,7,8和9。

示例2:我们的迁移可用版本9,数据库版本为9。
迁移什么都不做。

 

命令-清理

删除配置的模式中的所有对象。清理对开发和测试有很大帮助。通过彻底清除已配置的模式,它将有效地为您提供一个全新的开始。将删除所有对象(表,视图,过程......)。

注:请不要在生产库执行此操作。

Flyway 数据库脚本版本控制工具_第4张图片

 

命令-信息 

打印有关所有迁移的详细信息和状态信息。信息让您知道自己的位置。您可以一目了然地看到哪些迁移已经应用,哪些迁移尚未处理,何时执行以及它们是否成功。

 Flyway 数据库脚本版本控制工具_第5张图片

 

命令-验证

根据可用的迁移验证应用的迁移。验证可帮助您验证应用于数据库的迁移是否与本地可用的迁移相匹配。这对于检测可能阻止您可靠地重新创建架构的意外更改非常有用。

Flyway 数据库脚本版本控制工具_第6张图片

 

命令-撤销

撤消最近应用的版本化迁移。如果target已指定,Flyway将尝试按照应用顺序撤消版本化迁移,直到它遇到版本低于目标的版本。如果group处于活动状态,Flyway将尝试在单个事务中撤消所有这些迁移。如果没有要撤消的版本化迁移,则调用undo无效。没有可重复迁移的撤消功能。在这种情况下,应修改可重复迁移以包括所需的旧状态,然后使用migrate重新应用。

 Flyway 数据库脚本版本控制工具_第7张图片

 注:该命令的设计初衷不错,但是实际上不实用,因为不是所有的命令都支持回滚。

 

命令-底线

基准现有数据库,不包括所有迁移,包括baselineVersion。基线是将Flyway引入现有数据库,方法是将它们基于特定版本。这将导致迁移忽略所有迁移,包括基线版本。然后将照常应用较新的迁移。

Flyway 数据库脚本版本控制工具_第8张图片

 

命令-修复

修复架构历史记录表,修复是修复模式历史记录表问题的工具。它有两个主要用途:

  • 删除失败的迁移条目(仅适用于不支持DDL事务的数据库)
  • 将应用的迁移的校验和,描述和类型与可用的迁移重新对齐

Flyway 数据库脚本版本控制工具_第9张图片

 

JAVA项目应用

  • 第一步,在pom.xml中增加flyway的依赖:

    org.flywaydb
    flyway-core
    5.2.4

 

  • 第二步,按Flyway的规范创建版本化的SQL脚本。
  1. 在工程的src/main/resources目录下创建db目录,然后在db目录下创建migration目录

          Flyway 数据库脚本版本控制工具_第10张图片

  1. 在migration目录下创建版本化的SQL脚本V2__export.sql , 如果不创建脚本启动会报错,应该是有什么参数需要配置。
CREATE TABLE `flyway_test` (
  `id` int(4) NOT NULL,
  `name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

 

  • 第三步,在application.yml文件中配置Flyway属性(在spring节点下)。

   如果没有特殊要求,那么默认的配置参数即可满足需求,不需要额外配置。

  flyway:
    baseline-on-migrate: true

  按住CTRL键,鼠标左键单击,即可发现flyway的配置参数有哪些,目前最新的版本是 6.0.0 测试版本,配置参数很多会在6.0.x    之后废弃掉,这里需要注意下:

2019-05-13 10:43:29  WARN 51032 [main] org.flywaydb.core.Flyway : Direct configuration of the Flyway object has been deprecated and will be removed in Flyway 6.0. Use Flyway.configure() instead.
2019-05-13 10:43:29  WARN 51032 [main] org.flywaydb.core.Flyway : Direct configuration of the Flyway object has been deprecated and will be removed in Flyway 6.0. Use Flyway.configure() instead.

  Flyway 数据库脚本版本控制工具_第11张图片

 这里贴俩个关键的配置类,springboot2默认是集成了flyway的,如果配置了datasource,那么不需要配置flyway的user/password/url 参数。如果使用druid的数据源,那么需要修改一个配置参数“wall”,它会组织flyway执行脚本:

    # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
    filters: stat,slf4j
package org.springframework.boot.autoconfigure.flyway;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(
    prefix = "spring.flyway",
    ignoreUnknownFields = true
)
public class FlywayProperties {
    private List locations = new ArrayList(Collections.singletonList("classpath:db/migration"));
    private boolean checkLocation = true;
    private boolean enabled = true;
    private String user;
    private String password;
    private String url;
    private List initSqls = new ArrayList();

    public FlywayProperties() {
    }

    。。。
}
package org.flywaydb.core.api.configuration;

import java.io.File;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Map.Entry;
import javax.sql.DataSource;
import org.flywaydb.core.api.FlywayException;
import org.flywaydb.core.api.Location;
import org.flywaydb.core.api.MigrationVersion;
import org.flywaydb.core.api.callback.Callback;
import org.flywaydb.core.api.callback.FlywayCallback;
import org.flywaydb.core.api.errorhandler.ErrorHandler;
import org.flywaydb.core.api.logging.Log;
import org.flywaydb.core.api.logging.LogFactory;
import org.flywaydb.core.api.resolver.MigrationResolver;
import org.flywaydb.core.internal.callback.LegacyCallback;
import org.flywaydb.core.internal.configuration.ConfigUtils;
import org.flywaydb.core.internal.jdbc.DriverDataSource;
import org.flywaydb.core.internal.license.FlywayProUpgradeRequiredException;
import org.flywaydb.core.internal.util.ClassUtils;
import org.flywaydb.core.internal.util.Locations;
import org.flywaydb.core.internal.util.StringUtils;

public class ClassicConfiguration implements Configuration {
    private static final Log LOG = LogFactory.getLog(ClassicConfiguration.class);
    private String driver;
    private String url;
    private String user;
    private String password;
    private DataSource dataSource;
    private int connectRetries;
    private String initSql;
    private ClassLoader classLoader;
    private Locations locations;
    private Charset encoding;
    private String[] schemaNames;
    private String table;
    private MigrationVersion target;
    private boolean placeholderReplacement;
    private Map placeholders;
    private String placeholderPrefix;
    private String placeholderSuffix;
    private String sqlMigrationPrefix;
    private String repeatableSqlMigrationPrefix;
    private String sqlMigrationSeparator;
    private String[] sqlMigrationSuffixes;
    private boolean ignoreMissingMigrations;
    private boolean ignoreIgnoredMigrations;
    private boolean ignorePendingMigrations;
    private boolean ignoreFutureMigrations;
    private boolean validateOnMigrate;
    private boolean cleanOnValidationError;
    private boolean cleanDisabled;
    private MigrationVersion baselineVersion;
    private String baselineDescription;
    private boolean baselineOnMigrate;
    private boolean outOfOrder;
    private final List callbacks;
    private boolean skipDefaultCallbacks;
    private MigrationResolver[] resolvers;
    private boolean skipDefaultResolvers;
    private boolean mixed;
    private boolean group;
    private String installedBy;

    public ClassicConfiguration() {
        this.classLoader = Thread.currentThread().getContextClassLoader();
        this.locations = new Locations(new String[]{"db/migration"});
        this.encoding = Charset.forName("UTF-8");
        this.schemaNames = new String[0];
        this.table = "flyway_schema_history";
        this.placeholderReplacement = true;
        this.placeholders = new HashMap();
        this.placeholderPrefix = "${";
        this.placeholderSuffix = "}";
        this.sqlMigrationPrefix = "V";
        this.repeatableSqlMigrationPrefix = "R";
        this.sqlMigrationSeparator = "__";
        this.sqlMigrationSuffixes = new String[]{".sql"};
        this.ignoreFutureMigrations = true;
        this.validateOnMigrate = true;
        this.baselineVersion = MigrationVersion.fromVersion("1");
        this.baselineDescription = "<< Flyway Baseline >>";
        this.callbacks = new ArrayList();
        this.resolvers = new MigrationResolver[0];
    }

    public ClassicConfiguration(ClassLoader classLoader) {
        this.classLoader = Thread.currentThread().getContextClassLoader();
        this.locations = new Locations(new String[]{"db/migration"});
        this.encoding = Charset.forName("UTF-8");
        this.schemaNames = new String[0];
        this.table = "flyway_schema_history";
        this.placeholderReplacement = true;
        this.placeholders = new HashMap();
        this.placeholderPrefix = "${";
        this.placeholderSuffix = "}";
        this.sqlMigrationPrefix = "V";
        this.repeatableSqlMigrationPrefix = "R";
        this.sqlMigrationSeparator = "__";
        this.sqlMigrationSuffixes = new String[]{".sql"};
        this.ignoreFutureMigrations = true;
        this.validateOnMigrate = true;
        this.baselineVersion = MigrationVersion.fromVersion("1");
        this.baselineDescription = "<< Flyway Baseline >>";
        this.callbacks = new ArrayList();
        this.resolvers = new MigrationResolver[0];
        if (classLoader != null) {
            this.classLoader = classLoader;
        }

    }

}

配置参数解释:

flyway.baseline-description对执行迁移时基准版本的描述.
flyway.baseline-on-migrate当迁移时发现目标schema非空,而且带有没有元数据的表时,是否自动执行基准迁移,默认false.
flyway.baseline-version开始执行基准迁移时对现有的schema的版本打标签,默认值为1.
flyway.check-location检查迁移脚本的位置是否存在,默认false.
flyway.clean-on-validation-error当发现校验错误时是否自动调用clean,默认false.
flyway.enabled是否开启flywary,默认true.
flyway.encoding设置迁移时的编码,默认UTF-8.
flyway.ignore-failed-future-migration当读取元数据表时是否忽略错误的迁移,默认false.
flyway.init-sqls当初始化好连接时要执行的SQL.
flyway.locations迁移脚本的位置,默认db/migration.
flyway.out-of-order是否允许无序的迁移,默认false.
flyway.password目标数据库的密码.
flyway.placeholder-prefix设置每个placeholder的前缀,默认${.
flyway.placeholder-replacementplaceholders是否要被替换,默认true.
flyway.placeholder-suffix设置每个placeholder的后缀,默认}.
flyway.placeholders.[placeholder name]设置placeholder的value
flyway.schemas设定需要flywary迁移的schema,大小写敏感,默认为连接默认的schema.
flyway.sql-migration-prefix迁移文件的前缀,默认为V.
flyway.sql-migration-separator迁移脚本的文件名分隔符,默认__
flyway.sql-migration-suffix迁移脚本的后缀,默认为.sql
flyway.tableflyway使用的元数据表名,默认为schema_version
flyway.target迁移时使用的目标版本,默认为latest version
flyway.url迁移时使用的JDBC URL,如果没有指定的话,将使用配置的主数据源
flyway.user迁移数据库的用户名
flyway.validate-on-migrate迁移时是否校验,默认为true.

 

  • 第四步,运行项目

 flyway相关日志信息:

2019-05-13 11:21:26  INFO 27236 [main] org.flywaydb.core.internal.license.VersionPrinter : Flyway Community Edition 5.2.4 by Boxfuse
2019-05-13 11:21:26  INFO 27236 [main] org.flywaydb.core.internal.database.DatabaseFactory : Database: jdbc:mysql://192.168.200.51:3306/ems (MySQL 5.7)
2019-05-13 11:21:26  INFO 27236 [main] org.flywaydb.core.internal.command.DbValidate : Successfully validated 1 migration (execution time 00:00.094s)
2019-05-13 11:21:26  INFO 27236 [main] o.f.core.internal.schemahistory.JdbcTableSchemaHistory : Creating Schema History table: `ems`.`flyway_schema_history`
2019-05-13 11:21:27  INFO 27236 [main] org.flywaydb.core.internal.command.DbBaseline : Successfully baselined schema with version: 1
2019-05-13 11:21:27  INFO 27236 [main] org.flywaydb.core.internal.command.DbMigrate : Current version of schema `ems`: 1
2019-05-13 11:21:27  INFO 27236 [main] org.flywaydb.core.internal.command.DbMigrate : Schema `ems` is up to date. No migration necessary.

Flyway 数据库脚本版本控制工具_第12张图片

数据库的初始化工作已经做完,由于默认的版本号是1,所以新建的脚本没有执行。 

  • 第五步,修改SQL脚本文件名,再次启动应用

    将 “V1_export.sql” 修改为:“V2_export.sql”,重新启用应用,日志如下:

2019-05-13 11:29:22  INFO 27840 [main] org.flywaydb.core.internal.license.VersionPrinter : Flyway Community Edition 5.2.4 by Boxfuse
2019-05-13 11:29:22  INFO 27840 [main] org.flywaydb.core.internal.database.DatabaseFactory : Database: jdbc:mysql://192.168.200.51:3306/ems (MySQL 5.7)
2019-05-13 11:29:22  INFO 27840 [main] org.flywaydb.core.internal.command.DbValidate : Successfully validated 2 migrations (execution time 00:00.141s)
2019-05-13 11:29:22  INFO 27840 [main] org.flywaydb.core.internal.command.DbMigrate : Current version of schema `ems`: 1
2019-05-13 11:29:22  INFO 27840 [main] org.flywaydb.core.internal.command.DbMigrate : Migrating schema `ems` to version 2 - export
2019-05-13 11:29:23  INFO 27840 [main] org.flywaydb.core.internal.command.DbMigrate : Successfully applied 1 migration to schema `ems` (execution time 00:00.551s)

 

 不做任何修改重新启动后

2019-05-13 11:35:58  INFO 30420 [main] org.flywaydb.core.internal.license.VersionPrinter : Flyway Community Edition 5.2.4 by Boxfuse
2019-05-13 11:35:58  INFO 30420 [main] org.flywaydb.core.internal.database.DatabaseFactory : Database: jdbc:mysql://192.168.200.51:3306/ems (MySQL 5.7)
2019-05-13 11:35:58  INFO 30420 [main] org.flywaydb.core.internal.command.DbValidate : Successfully validated 2 migrations (execution time 00:00.154s)
2019-05-13 11:35:58  INFO 30420 [main] org.flywaydb.core.internal.command.DbMigrate : Current version of schema `ems`: 2
2019-05-13 11:35:58  INFO 30420 [main] org.flywaydb.core.internal.command.DbMigrate : Schema `ems` is up to date. No migration necessary.
  • 第六步,将已经执行的脚本,修改文件内容再次执行一遍(验证flyway的脚本校验功能)

  将 “id”字段修改为“ids”

CREATE TABLE `flyway_test` (
  `ids` int(4) NOT NULL,
  `name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

再次启动,后台报错,日志如下:

 

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'flywayInitializer' defined in class path resource [org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration$FlywayConfiguration.class]: Invocation of init method failed; nested exception is org.flywaydb.core.api.FlywayException: Validate failed: Migration checksum mismatch for migration version 2
-> Applied to database : -1894925178
-> Resolved locally    : 2074103328
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1710)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:583)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:502)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:312)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:310)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:758)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:868)
	at org.springframework.context.support.AbstractApplicationContext.__refresh(AbstractApplicationContext.java:549)
	at org.springframework.context.support.AbstractApplicationContext.jrLockAndRefresh(AbstractApplicationContext.java:40002)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:41008)
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:138)
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:751)
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:387)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:327)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1245)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1233)
	at com.ems.EmsSpringBootApplication.main(EmsSpringBootApplication.java:19)
Caused by: org.flywaydb.core.api.FlywayException: Validate failed: Migration checksum mismatch for migration version 2
-> Applied to database : -1894925178
-> Resolved locally    : 2074103328
	at org.flywaydb.core.Flyway.doValidate(Flyway.java:1482)
	at org.flywaydb.core.Flyway.access$100(Flyway.java:85)
	at org.flywaydb.core.Flyway$1.execute(Flyway.java:1364)
	at org.flywaydb.core.Flyway$1.execute(Flyway.java:1356)
	at org.flywaydb.core.Flyway.execute(Flyway.java:1711)
	at org.flywaydb.core.Flyway.migrate(Flyway.java:1356)
	at org.springframework.boot.autoconfigure.flyway.FlywayMigrationInitializer.afterPropertiesSet(FlywayMigrationInitializer.java:66)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1769)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1706)
	... 18 common frames omitted

因此按照版本号递增脚本,即可实现脚本的增量部署。R开头的脚本会在应用每次启动后重复执行,适合放配置数据DML脚本。V开头的只会执行一次,优先级高于R开头的脚本。

Flyway 数据库脚本版本控制工具_第13张图片

 

插件支持

有如下插件支持:Maven, Gradle,SBT和Ant,还有更多Plugins,提供对Spring Boot, Dropwizard, Grails, Play,Griffon, Grunt, Ninja 的支持。其中可以研究下flyway-test-extensions,有Usage flyway dbunit test、Usage-flyway-spring-test 功能很强大。

POM依赖


    org.flywaydb
    flyway-maven-plugin
    5.2.4



参考:

Flyway官网 https://flywaydb.org/documentation/

你可能感兴趣的:(数据库)