一、前言

一般在我们开发项目的时候经常会更新数据库表的字段,如果同事 a 添加了表字段,没有及时给同事 b sql 脚本,可能同事 b 的代码运行就会报错,而且随着时间的推移,sql 脚本越来越多,项目上线的时候整理起来就很费时间,所以就有大佬级别的人物创造了 Flyway 这个数据库版本管理工具。Flyway 是一款开源的数据库版本管理工具,它更倾向于规约优于配置的方式。Flyway 可以独立于应用实现管理并跟踪数据库变更,支持数据库版本自动升级,并且有一套默认的规约,不需要复杂的配置,Migrations 可以写成 SQL 脚本,也可以写在 Java 代码中,不仅支持 Command Line 和 Java API,还支持 Build 构建工具和 Spring Boot 等,同时在分布式环境下能够安全可靠地升级数据库,同时也支持失败恢复等。

二、Flyway介绍

2.1 主要特性

1.普通 SQL:纯 SQL 脚本(包括占位符替换)没有专有的XML格式,没有锁定
2.无限制:使用 Java 代码来进行一些高级数据操作
3.零依赖:只需运行在 Java6(及以上)和数据库所需的 JDBC 驱动
4.约定优于配置:迁移时,自动查找系统文件和类路径中的 SQL 文件或 Java 类
5.高可靠性:在集群环境下进行数据库升级是安全可靠的
6.云支持:完全支持 Microsoft SQL Azure, Google Cloud SQL & App Engine、Heroku Postgres 和 Amazon RDS
7.自动迁移:使用 Flyway 提供的 API,让应用启动和迁移同时工作
8.快速失败:损坏的数据库或失败的迁移可以防止应用程序启动
9.数据库清理:在一个数据库中删除所有的表、视图、触发器,而不是删除数据库本身

2.2 运行原理

当 Flyway 连接数据库中的 schema 后,会先检查是否已存在 flyway_schema_history 表,如果没有则创建。该表用于跟踪数据库的状态,如数据迁移的版本,迁移成功状态等信息。

当 flyway_schema_history 存在后,Flyway 会扫描文件系统或应用中的 classpath 目录的数据迁移文件,然后根据它们的版本号进行按序迁移,如下图:

由于 flyway_schema_history 表中记录了迁移的版本号,如果文件的版本号小于或等于标记为当前版本的版本号,则忽略它们不执行。

在实际使用中,flyway有两种方式执行,一种是构建项目的时候执行,一种是项目启动的时候执行。

上边描述的内容或许对读者来说还不够直观,那么下面我们就开始进行实战演练。

三、构建实战

环境:

mysql-5.7.30
springboot 2.3.5.RELEASE

新建一个 Maven 项目。

3.1 添加依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
        <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

注意:如果构件项目的时候执行Flyway,那么就必须引入spring-boot-starter-jdbc依赖。

3.2 配置数据迁移文件

在项目的 src/main/resources 下创建 db/migration 目录,该目录下放置需要数据迁移的文件。

数据迁移文件默认名称格式为:V[version]__[name].sql。

注意:名称中[version]和[name]之间是两个下划线!

本次测试新建名为 V0.1.0__init_table.sql 的文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
USE FLYWAYDB;

DROP TABLE IF EXISTS user;

CREATE TABLE user
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id)
);

我们再新建一个名为 V0.1.1__init_data.sql 文件,内容如下:

1
2
3
4
5
6
INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');

版本 1 数据迁移的内容是创建一张 user 表。

3.3 配置application.yml

1
2
3
4
5
6
7
8
9
10
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/flywaydb?createDatabaseIfNotExist=true&serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
username: root
password: 19920503

flyway:
enabled: true # 禁止清理数据库表
table: flywayinit #修改原数据表名,默认为schema_version

编译项目结果如下图:

图中,数据库 flywaydb 中创建了 flywayinit 表和 user 表,数据成功迁移到指定数据库中。

当系统升级时又需要做数据迁移,我们只需在 db/migration 目录下再放置新版本的 sql 文件即可。

版本 0.1.1 的数据迁移内容是往 user 表中插入 3 条数据。

上面我们在application.yml配置了flyway初始化的数据库,其实也可以配置好多相关参数,具体如下:

配置项含义
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.

四、启动方式实践

4.1 引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
        <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>

4.2 修改application.yml

1
2
3
4
5
6
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/flywaydb?createDatabaseIfNotExist=true&serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
username: root
password: 19920503

4.3 编码

创建配置类FlywayConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Configuration
public class FlywayConfig {

@Value("${spring.datasource.url}")
private String url;

@Value("${spring.datasource.username}")
private String username;

@Value("${spring.datasource.password}")
private String password;

@PostConstruct
public void migrate() {
Flyway flyway = Flyway.configure().dataSource(url, username, password).table("flyway_init_table").load();

// 创建 flyway_schema_history 表
// flyway.baseline();

// 删除 flyway_schema_history 表中失败的记录
// flyway.repair();

// 检查 sql 文件
// flyway.validate();

// 执行数据迁移
flyway.migrate();

// 删除当前 schema 下所有表
// flyway.clean();
}
}

在构建flyway的时候,构件方法和配置中的配置项类似。

4.4 启动测试

启动项目,结果如下图: