Spring Boot 使用 flyway

本贴最后更新于 1984 天前,其中的信息可能已经时过境迁

概念性问题参考 官方文档

概述

Flyway 是一款开源的数据库版本管理工具,它更倾向于规约优于配置的方式。

它基于 7 个基本命令: Migrate, Clean, Info, Validate, Undo, Baseline and Repair.

Migrations 支持纯 sql 语句 或 java 代码。

它有一个命令行客户端。如果您在 JVM 上,我们建议使用 Java API(也适用于 Android)在应用程序启动时迁移数据库。或者,您也可以使用 Maven 插件或 Gradle 插件。

如果这还不够,可以使用 Spring Boot,Dropwizard,Grails,Play,SBT,Ant,Griffon,Grunt,Ninja 等插件!

支持的数据库是 Oracle,SQL Server(包括 Amazon RDS 和 Azure SQL 数据库,DB2,MySQL(包括 Amazon RDS,Azure 数据库和 Google Cloud SQL),Aurora MySQL,MariaDB,Percona XtraDB 集群,PostgreSQL(包括 Amazon RDS,Azure 数据库,Google Cloud SQL 和 Heroku),Aurora PostgreSQL,Redshift,CockroachDB,SAP HANA,Sybase ASE,Informix,H2,HSQLDB,Derby 和 SQLite。迁移

通过官方文档对 flyway 有了个初步认识,因为主要使用其 Migrations 功能 ,所以着重看一下这部分。

Migrate (迁移)

官方对其的描述是 Migrates the schema to the latest version. 将架构迁移到最新版本。

看描述就是执行程序中的 SQL 脚本文件并更新(如果不存在会先创建)flyway_schema_history 表。

null

其中执行 SQL 脚本就相当于是在更新数据库版本,执行过的脚本文件信息会在 flyway_schema_history 表中生成一条记录。

迁移最好在应用程序启动时执行,以避免数据库和代码期望之间的任何不兼容型。即代码中用到的数据结构是要 migrate 过的新的数据结构,如果 migrate 设定在程序执行中的某个节点再执行,可能就会造成部分代码与数据库不兼容问题。

执行规则:

  1. 执行是按版本号顺序进行的,比如当前数据库版本是 5,程序中新增了版本号为 6、7、8、9 的脚本文件,那么下次程序运行时会顺序执行 6、7、8、9 脚本。

  2. 执行过的脚本文件不会再被执行。

    (注:只有以 V 开头的版本控制型脚本是这样)

Migration(迁移)

使用 Flyway 对数据库的所有更改都称为迁移。迁移可以是**versioned (版本化)的,也可以是repeatable(可重复)的。版本化迁移有两种形式:常规撤销**。

脚本文件命名规则决定了脚本的 migration 类型

为了能够被 flyway 识别,迁移脚本必须符合以下命名规则:

flywayNaming.png

文件名由以下部分组成:

  • Prefix(前缀):V 开头的代表版本化迁移, U 开头代表 撤销迁移, R 开头代表可重复迁移 (前缀可通过配置文件设置为别的字符)

  • Version(版本):版本迁移时的版本号,必要且不能重复,通常为递增整数,若有需要,也可以使用 . 表示小版本更新,比如下列版本号都是符合要求的

    • 1

    • 001

    • 5.2

    • 1.2.3.4.5.6.7.8.9

    • 205.68

    • 20130115113556

    • 2013.1.15.11.35.56

    • 2013.01.15.11.35.56

    撤销迁移的

  • Separator(分隔符): __ 双下划线 (可通过配置文件设置为别的字符)

  • Description(描述): 用于描述脚本文件执行的内容,单词使用单下划线或空格分隔

  • Suffix(后缀): 因为此处使用的 SQL 脚本执行的更新,所以后缀使用 .sql ,如果是基于 Java 的迁移,因为创建的是 java 文件,也就不需要后缀。(可通过配置文件设置为别的字符)

在 spring-boot 项目中使用 flyway

项目中使用原来的 mysql 数据库,所以只在 Maven 中引入 flyway 即可。

<dependency>  
 <groupId>org.flywaydb</groupId>  
 <artifactId>flyway-core</artifactId>  
 <version>5.2.4</version>  
</dependency>

Gradle 参考官方文档 Gradle

各种版本的资源可在 mvnrepository 找到。

在 application.properties 文件中配置一些基本属性。

# 是否开启flyway,默认是 true,如果打算一直用flyway可以不用配置。当某次执行代码时不想用flyway 时可以把此项设为false;  
spring.flyway.enabled=true  
# 当迁移时发现目标schema非空,而且带有没有元数据的表时,是否自动执行基准迁移,默认false.  
spring.flyway.baseline-on-migrate=true  
#  配合 baseline-on-migrate 使用,因为创建 flyway_schema_history 表时也会占用一条记录,如果不加初始版本=0,那么V1里的sql就不会执行
# (这个问题我一开始还以为它就是这么设计的,第一个版本我以为就是被占用的,直到我发现别人的是可以执行的。。。。)
spring.flyway.baseline-version=0
# 迁移脚本的位置,默认db/migration.  
spring.flyway.locations=classpath:/db/migration

第一次执行时 baseline-on-migrate 一定要设为 true,否则会报异常

  
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: Found non-empty schema(s) `springboot` without schema history table! Use baseline() or set baselineOnMigrate to true to initialize the schema history table.  

其它配置参考 SpringBoot 配置属性之 Migration

配置完后 就在配置的 locations 的位置创建 对应的文件夹,然后写好脚本文件启动程序就行了。

1558603275921.png
1558603377682.png
1558604317925.png

为了测试上文提到的版本执行规则,我先删除了 flyway_schema_history 表,重新执行的时候就依次执行了几个 V 开头的 sql 脚本。

根据 flyway_schema_history 表中的记录显示,最后还执行了 R 开头的可重复脚本。注意:虽然 R 是 可重复执行的,但只有文本发生改变时才会重复执行,如果这个脚本没有修改过,它就不会重复执行。注意:R 文件执行时并不是只执行追加的部分,它依然是全文执行。

还有一点就是 U 开头的那个撤销迁移 脚本 没有执行。查了一下官方文档,说是只有 专业版企业版才支持 undo 这个功能,不知道这里没有执行是不是这个原因。而且官方文档也提到这个功能并不如设想那样好用,所以这里也就不再深究了。

对于执行的 SQL 脚本,Flyway 不仅支持常规的 SQL 语法,还支持使用可配置的前缀和后缀替换占位符。

示例:

/* Single line comment */  
CREATE TABLE test_user (  
 name VARCHAR(25) NOT NULL,  
 PRIMARY KEY(name)  
);  
/*  
Multi-line  
comment  
*/  
-- Placeholder  
INSERT INTO ${tableName} (name) VALUES ('Mr. T');

以上是基于 SQL 的迁移。

下面看一下基于 Java 的迁移。

命名规则与 SQL 文件基本一致,只是这里由于要创建的是 Java 文件,所以命名时不能使用 "." 号进行小版本控制。可以使用 "_" 单下划线。如

V1__add_new_table

V1_1__alter_table 这样的命名。

脚本文件支持 使用 JDBC。

package db.migration;  
  
import org.flywaydb.core.api.migration.BaseJavaMigration;  
import org.flywaydb.core.api.migration.Context;  
import java.sql.PreparedStatement;  
  
/**  
 * Example of a Java-based migration.  
 * 由源码可看到 BaseJavaMigration 是一个抽象类,它实现了 JavaMigration 接口的几乎所有方法,  
 * 所以这里只要 重载 migrate 方法即可。  
 */  
public class V1_2__Another_user extends BaseJavaMigration {  
 public void migrate(Context context) throws Exception {  
 try (PreparedStatement statement =   
 context  
 .getConnection()  
 .prepareStatement("INSERT INTO test_user (name) VALUES ('Obelix')")) {  
 statement.execute();  
 }  
 }  
}

如果不想使用 JDBC,也可以使用 Spring JDBC 的 JdbcTemplate 对象

package db.migration;  
  
import org.flywaydb.core.api.migration.BaseJavaMigration;  
import org.flywaydb.core.api.migration.Context;  
import java.sql.PreparedStatement;  
  
/**  
 * Example of a Java-based migration using Spring JDBC.  
 */  
public class V1_2__Another_user extends BaseJavaMigration {  
 public void migrate(Context context) {  
 new JdbcTemplate(new SingleConnectionDataSource(context.getConnection(), true))  
 .execute("INSERT INTO test_user (name) VALUES ('Obelix')");  
 }  
}

1558669370849.png

下面看一下 flyway_schema_history 表的字段

CREATE TABLE `flyway_schema_history` (  
 `installed_rank` int(11) NOT NULL, // 脚本是第几个执行的,就是一个顺序自增的整数  
 `version` varchar(50) DEFAULT NULL, // 脚本文件名中设定的版本号  
 `description` varchar(200) NOT NULL, // 脚本文件名中双下划线后的描述内容(第一条记录是特例,它是生成表的记录,这就造成了一个问题,你的第一个脚本文件无法命名为V1,因                                  //  version = 1 已经在第一条记录里被占用了,也就是说你的 V1 脚本无法被执行)  
 `type` varchar(20) NOT NULL, // sql 脚本显示为 SQL java脚本显示为 JDBC  
 `script` varchar(1000) NOT NULL, //配置的文件地址 + 文件名,sql有后缀,java没有后缀  
 `checksum` int(11) DEFAULT NULL, // 校验和。用于检测意外的更改  
 `installed_by` varchar(100) NOT NULL, //脚本执行人 (就是配置的数据库用户)  
 `installed_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, //脚本执行的时间点 YYYY-MM-dd HH:mm:ss  
 `execution_time` int(11) NOT NULL, // 脚本执行花费毫秒数  
 `success` tinyint(1) NOT NULL, //脚本是否执行成功。1 成功;0 失败  
 PRIMARY KEY (`installed_rank`),  
 KEY `flyway_schema_history_s_idx` (`success`)  
) ENGINE=InnoDB DEFAULT CHARSET=utf8;  

checksum

字段是通过计算脚本文件得出的一串数字(具体的计算方法我在源码中也没找到)。官方文档上描述它 用于检测意外的更改。我看比较明显的作用是 判断可重复执行脚本是否发生改变。这里我做了个实验,先运行了一个可重复执行脚本(记录 6),然后修改了一下这个脚本,再次启动程序,可以看到它又执行了(记录 7),而且两条记录的 checksum 并不相同。接下来我又把这个脚本改回 6 执行的状态,再次启动程序,发现它又执行了(记录 8),而这条记录的 checksum 和记录 6 的 checksum 是相同的。由此基本可以得出 checksum 是通过计算脚本内容得出的。

官方文档上在 基于 Java 的迁移中也提到了这个字段。

Unlike SQL migrations, Java migrations by default do not have a checksum and therefore do not participate in the change detection of Flyway’s validation. This can be remedied by implementing the getChecksum() method, which you can then use to provide your own checksum, which will then be stored and validated for changes.

可以看出 基于 Java 的迁移没有 checksum,flyway_schema_history 表中这个字段是空的。所以需要自己去实现 getCheckSum() 方法.

public class R__insert extends BaseJavaMigration {  
 public static final String sql = "insert into people2 (name,age) values ('聂小倩',19)";  
 @Override  
 public void migrate(Context context) throws Exception {  
 new JdbcTemplate(new SingleConnectionDataSource(context.getConnection(), true))  
 .execute(sql);  
 }  
 @Override  
 public Integer getChecksum() {  
 return sql.hashCode();  
 }

这样 java 的可重复执行脚本就和 sql 的基本一样了。

  • Spring

    Spring 是一个开源框架,是于 2003 年兴起的一个轻量级的 Java 开发框架,由 Rod Johnson 在其著作《Expert One-On-One J2EE Development and Design》中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 JavaEE 应用程序开发提供集成的框架。

    942 引用 • 1459 回帖 • 31 关注
  • flyway
    1 引用
  • migration
    1 引用
1 操作
mnizht 在 2019-05-31 17:21:39 更新了该帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...