项目整合flyway

504 阅读9分钟

前言

数据库版本管理在软件项目开发中是一个弱点,大部分软件项目缺乏相应的数据库版本管理,缺乏相应配套的管理规范,导致版本管理不完善,无法与相关发布版本匹配,为后续持续集成与发布,软件测试,生产环境部署、升级工作带来了非常大的困难。

为了规范数据库版本管理,特对数据库版本管理约定如下:

  1. 工程代码中包含数据库版本脚本,脚本版本与工程代码版本是实时匹配。
  2. 数据库版本管理支持持续发布。
  3. 工程代码包含的数据库脚本是全面的,不存在隐含的需要配置的功能。
  4. 数据库版本管理脚本具有良好可读性和规范,支持相关自动化工具自动执行。
  5. 提供相关数据库迁移工具,为部分配置数据提供生成迁移脚本。

现使用Flyway工具进行数据库版本管理。

相关工具:
jdk版本:8
maven版本:3.8.3
Flyway版本:9.16.1

一、Flyway的特性

  • 自动升级(自动发现更新项):Flyway 会将任意版本的数据库升级到最新版本。Flyway 可以脱离JVM 环境通过命令行执行,可以通过Ant 脚本执行,通过Maven 脚本执行(这样就可以在集成环境自动执行),并且可以在应用中执行(比如在应用启动时执行)。
  • 规约优于配置:Flyway 有一套默认的规约,所以不需要修改任何配置就可以正常使用。
  • 既支持SQL 脚本,又支持Java 代码:可以使用SQL 脚本执行数据库更新,也可以使用Java 代码来进行一些高级数据升级操作。
  • 高可靠性:在集群环境下进行数据库升级是安全可靠的。
  • 支持清除已存在的库表结构:Flyway 可以清除已存在的库表结构,可以从零开始搭建您的库表结构,并管理您的数据库版本升级工作。
  • 支持失败修复。新的2.0 版本提供了repair 功能,用于解决数据库更新操作失败问题。

Flyway的注意事项

flyway 需要在 DB 中先创建一个 metdata 表 (缺省表名为 flyway_schema_history), 在该表中保存着每次 migration 的记录, 记录包含 migration 脚本的版本号和 SQL 脚本的 checksum 值. 当一个新的 SQL 脚本被扫描到后, Flyway 解析该 SQL 脚本的版本号, 并和 metadata 表已 apply 的的 migration 对比, 如果该 SQL 脚本版本更新的话, 将在指定的 DB 上执行该 SQL 文件, 否则跳过该 SQL 文件。两个 flyway 版本号的比较, 采用左对齐原则, 缺位用 0 代替. 举例如下:

1.2.9.4 比 1.2.9 版本高. 
1.2.10 比 1.2.9.4 版本高.
1.2.10 和 1.2.010 版本号一样高, 每个版本号部分的前导 0 会被忽略.

SQL脚本命名规范

Flyway SQL 文件可以分为两类: Versioned 和 Repeatable. Versioned migration 用于版本升级, 每个版本有唯一的版本号并只能 apply 一次. Repeatable migration 是指可重复加载的 migration, 一旦 SQL 脚本的 checksum 有变动, flyway 就会重新应用该脚本. 它并不用于版本更新, 这类的 migration 总是在 versioned migration 执行之后才被执行.默认情况下, Migration SQL的命名规则如下图:

image.png

文件名由以下部分组成:

  • Prefix:用于版本控制(V)、撤销(U)、和可重复迁移(R) (V、U、R支持配置修改)
  • Version:带有点或下划线的版本可根据需要分隔任意数量的部分(不适用于可重复的迁移)
  • Separator:(两个下划线) (支持配置修改)
  • Description:下划线或空格分隔单词
  • Suffix:.sql

完整文件名示例:
V年月日.小时.分钟__简明的介绍.sql

image.png

脚本存放目录:源代码/resources/db/migration,migration之后的目录自由配置,支持中文,建议一个环境(例如开发环境/xxx,开发环境/yyy)作为主目录,然后再细分。

二、开始整合

引入依赖

pom文件依赖:
flyway8之后的版本,以前的flyway-core需要变化,mysql版本改成flyway-mysql,oracle版本改成flyway-oracle等等。springboot项目自带flyway,不需要指明版本。

<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-mysql</artifactId>
    <version>9.16.1</version>
</dependency>

属性详解:

属性名解释
url数据库连接url,这个url指明了数据库也不影响多数据库同步,只需要在sql脚本中指明使用的是哪个数据库即可。
user数据库用户
password数据库密码
tableflyway执行历史记录表表名,flyway执行后,会在url指明的那个数据库建立一个表(默认表名为flyway_schema_history)
locationssql脚本的存放目录,约定目录:/resources/db/migration,migration之后的目录自由配置,支持中文,建议一个环境(例如开发环境/xxx,开发环境/yyy)作为主目录,然后再细分。
outOfOrder是否无序执行sql文件,设置为false,严格按照顺序执行
cleanDisabled是否禁用clean操作,如果启动clean操作会清空数据库中的所有数据
cleanOnValidationError是否在执行失败时清空数据库
baselineOnMigrate假如目标库有其他表,没有flyway操作记录表,则需要将这个属性设置为true
baselineVersion指定 baseline 的版本号,缺省值为 1, 低于该版本号的 SQL 文件, migrate 的时候被忽略。可设置
connectRetries尝试连接数据库时的最大重试次数,间隔时间1秒,默认是0不重试
group是否将迁移分组到同一个事务中。true:遇到异常所有操作回滚     false:遇到异常前的操作不回滚可以这样认为,在一次flyway.migrate()中,一个sql脚本在一个事务中, 下一个脚本执行失败了,不影响上一个执行成功的脚本。脚本执行失败,不会影响项目的运行。

核心代码

通过enable属性来决定是否使用flyway。

@Resource
private FlywayConfig flywayConfig;

@PostConstruct
public void init() {
    if (flywayConfig.isEnable()) {
        migrate();
    }
}

private void migrate() {
        Flyway flyway = Flyway.configure()
                .dataSource(flywayConfig.getUrl(), flywayConfig.getUsername(), flywayConfig.getPassword())
                .table(flywayConfig.getHistoryTable())
                .locations(flywayConfig.getSqlFilePath())
                .encoding(StandardCharsets.UTF_8)
                .outOfOrder(flywayConfig.isOutOfOrder())
                .cleanDisabled(flywayConfig.isCleanDisabled())
                .cleanOnValidationError(flywayConfig.isCleanOnValidationError())
                .baselineOnMigrate(flywayConfig.isBaselineOnMigrate())
                .baselineVersion(flywayConfig.getBaselineVersion())
                .connectRetries(flywayConfig.getConnectRetries())
                .group(flywayConfig.isGroup())
                .load();

        try {
            log.info("DatabaseFlywayMigration-->migrate:配置成功,即将执行sql语句");
            long l = System.currentTimeMillis();
            flyway.migrate();
            log.info("DatabaseFlywayMigration-->migrate:sql语句执行成功,flyway---END");
            log.info("本次执行耗时:" + (System.currentTimeMillis() - l) / 1000d + "秒");
        } catch (FlywayException e) {
            log.error("DatabaseFlywayMigration-->migrate:执行sql语句失败,请查看日志排查错误");
            // Flyway的repair方法用于修复受损的Flyway metadata表。
            // 在某些情况下,可能会发生数据库中的Flyway metadata表被意外修改或删除的情况,
            // 从而导致Flyway无法正常工作。在这种情况下,可以使用Flyway的repair方法来重新创建metadata表,
            // 并将其与已执行的脚本同步。这个方法将回滚所有已应用的脚本并重放它们,以确保所有的脚本都已正确应用。
            // 使用之后,执行失败的脚本不会被记录
            flyway.repair();
            log.info("info ====> " + flyway.info().toString());
            e.printStackTrace();
        }
}

locations说明

Flyway的locations方法可以传入多个参数,它的作用是指定要执行迁移脚本的路径。
多个参数可以用来同时指定多个路径,这样就可以将迁移脚本分组存放在不同的目录下。
这对于管理大型项目中的多个模块、多个服务或多个数据库非常有用。
如果指定多个路径,Flyway会按顺序依次扫描每个路径,并按照脚本文件名的前缀数字顺序执行每个路径下的迁移脚本。例如:

Flyway flyway = Flyway.configure()         
            .dataSource("jdbc:mysql://localhost:3306/mydb()", "username", "password")
            .locations("classpath:db/migration/common","filesystem:/opt/db/migration/custom")
            .load();
            
            flyway.migrate();

在这个例子中,Flyway会先扫描classpath下的db/migration/common目录,
并执行该目录下的所有迁移脚本;然后它会扫描/opt/db/migration/custom目录,
并执行该目录下的所有迁移脚本。这样就可以将公共的迁移脚本放在一个目录下,将每个服务或数据库特定的迁移脚本放在不同的目录下,
从而更好地管理整个项目的迁移过程。

多数据库使用

数据库连接的url指明了某个数据库也不影响多数据库同步,只需要在sql脚本中指明使用的是哪个数据库即可。

切换环境使用

flyway.xml配置文件里,复制当前bean标签的内容,然后注释当前bean标签的内容,写注释说明要用到什么环境,粘贴刚才复制的内容,修改相应的属性。

image.png

建议:

V开头的文件作为大范围同步时使用,如果因为新功能需要创建一个表,或者插入数据,可以建R__create-table.sql,R开头的会重复执行文件,区分是建表还是更新数据等,

执行完后,把里面的sql删除也没事(V开头的文件,把里面的sql删除会报错,影响执行)。

比如:R__update.sql,里面执行了更新的sql,然后去数据库手动更新,把数据改了,再启动R__update.sql的sql不会执行,手动更新的数据不会被覆盖。

注意:如果在R__update.sql里,更新语句的后面再添加更新语句,再启动,会从头开始执行sql,建议有新的sql要执行,把之前的删除再执行。特别是重复执行的sql里如果有建表语句,里面有如果表存在则删除的sql,然后又修改了那个sql,再次执行时,会把表删除了再新建。

问题:

1、删除了已经执行的sql脚本会有什么影响?

答:没有影响,第一次启动的时候会打印错误日志,说明这个sql脚本已经被删除了,再启动,错误日志也没有了,操作记录表会将这个版本的sql脚本type改成DELETE。

2、把已经标记删除的sql脚本放进目录再执行会出现什么情况?

答:不会执行这个脚本,并通过打印错误日志进行说明,提示可以设置忽略已执行i的脚本(gnoreIgnoredMigrations=true)。
但如果这个脚本的版本是比较前的,那后面版本的sql脚本都不会被执行。除非设置outOfOrder=true,设置为不按照版本顺序执行sql脚本。

image.png