Spring Boot 使用 flyway

Published on with 0 views and 0 comments

概念性问题参考 官方文档

概述

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 Boot 使用 flyway
作者:mnizht
地址:http://zhuht.xyz/articles/2019/06/03/1559556049700.html