# 数据库中间件 flyway

> Flyway 是一个数据迁移工具。
>
> 关键词：

## 简介 <a href="#jian-jie" id="jian-jie"></a>

### 什么是 Flyway？ <a href="#shi-mo-shi-flyway" id="shi-mo-shi-flyway"></a>

**Flyway 是一个开源的数据库迁移工具。**

### 为什么要使用数据迁移？ <a href="#wei-shi-mo-yao-shi-yong-shu-ju-qian-yi" id="wei-shi-mo-yao-shi-yong-shu-ju-qian-yi"></a>

为了说明数据迁移的作用，我们来举一个示例：

（1）假设，有一个叫做 Shiny 的项目，它的架构是一个叫做 Shiny Soft 的 App 连接叫做 Shiny DB 的数据库。

（2）对于大多数项目而言，最简单的持续集成场景如下所示：

这意味着，我们不仅仅要处理一份环境中的修改，由此会引入一些版本冲突问题：

在代码侧（即应用软件）的版本问题比较容易解决：

* 有方便的版本控制工具
* 有可复用的构建和持续集成
* 规范的发布和部署过程

那么，数据库层面的版本问题如何解决呢？

目前仍然没有方便的数据库版本工具。许多项目仍使用 sql 脚本来解决版本冲突，甚至是遇到冲突问题时才想起用 sql 语句去解决。

由此，引发一些问题：

* 机器上的数据库是什么状态？
* 脚本到底生效没有？
* 生产环境修复的问题是否也在测试环境修复了？
* 如何建立一个新的数据库实例？

数据迁移就是用来搞定这些混乱的问题：

* 通过草稿重建一个数据库。
* 在任何时候都可以清楚的了解数据库的状态。
* 以一种明确的方式将数据库从当前版本迁移到一个新版本。

### Flyway 如何工作？ <a href="#flyway-ru-he-gong-zuo" id="flyway-ru-he-gong-zuo"></a>

最简单的场景是指定 Flyway 迁移到一个空的数据库。

Flyway 会尝试查找它的 schema 历史表，如果数据库是空的，Flyway 就不再查找，而是直接创建数据库。

现再你就有了一个仅包含一张空表的数据库，默认情况下，这张表叫 \_flyway\_schema\_history\_。

这张表将被用于追踪数据库的状态。

然后，Flyway 将开始扫描文件系统或应用 classpath 中的 **migrations**。这些 **migrations** 可以是 sql 或 java。

这些 **migrations** 将根据他们的版本号进行排序。

任意 migration 应用后，schema 历史表将更新。当元数据和初始状态替换后，可以称之为：迁移到新版本。

Flyway 一旦扫描了文件系统或应用 classpath 下的 migrations，这些 migrations 会检查 schema 历史表。如果它们的版本号低于或等于当前的版本，将被忽略。保留下来的 migrations 是等待的 migrations，有效但没有应用。

migrations 将根据版本号排序并按序执行。

## 快速上手 <a href="#kuai-su-shang-shou" id="kuai-su-shang-shou"></a>

Flyway 有 4 种使用方式：

* 命令行
* JAVA API
* Maven
* Gradle

### 命令行 <a href="#ming-ling-hang" id="ming-ling-hang"></a>

适用于非 Java 用户，无需构建。

```
> flyway migrate -url=... -user=... -password=...
```

（1）**下载解压**

进入[官方下载页面](https://flywaydb.org/download/)，选择合适版本，下载并解压到本地。

（2）**配置 flyway**

编辑 `/conf/flyway.conf`：

```
flyway.url=jdbc:h2:file:./foobardb
flyway.user=SA
flyway.password=
```

（3）**创建第一个 migration**

在 `/sql` 目录下创建 `V1__Create_person_table.sql` 文件，内容如下：

```
create table PERSON (
    ID int not null,
    NAME varchar(100) not null
);
```

（4）**迁移数据库**

运行 Flyway 来迁移数据库：

```
flyway-5.1.4> flyway migrate
```

运行正常的情况下，应该可以看到如下结果：

```
Database: jdbc:h2:file:./foobardb (H2 1.4)
Successfully validated 1 migration (execution time 00:00.008s)
Creating Schema History table: "PUBLIC"."flyway_schema_history"
Current version of schema "PUBLIC": << Empty Schema >>
Migrating schema "PUBLIC" to version 1 - Create person table
Successfully applied 1 migration to schema "PUBLIC" (execution time 00:00.033s)
```

（5）**添加第二个 migration**

在 `/sql` 目录下创建 `V2__Add_people.sql` 文件，内容如下：

```
insert into PERSON (ID, NAME) values (1, 'Axel');
insert into PERSON (ID, NAME) values (2, 'Mr. Foo');
insert into PERSON (ID, NAME) values (3, 'Ms. Bar');
```

运行 Flyway

```
flyway-5.1.4> flyway migrate
```

运行正常的情况下，应该可以看到如下结果：

```
Database: jdbc:h2:file:./foobardb (H2 1.4)
Successfully validated 2 migrations (execution time 00:00.018s)
Current version of schema "PUBLIC": 1
Migrating schema "PUBLIC" to version 2 - Add people
Successfully applied 1 migration to schema "PUBLIC" (execution time 00:00.016s)
```

### JAVA API <a href="#java-api" id="java-api"></a>

（1）**准备**

* Java8+
* Maven 3.x

（2）**添加依赖**

在 `pom.xml` 中添加依赖：

```
<project ...>
    ...
    <dependencies>
        <dependency>
            <groupId>org.flywaydb</groupId>
            <artifactId>flyway-core</artifactId>
            <version>5.1.4</version>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.3.170</version>
        </dependency>
        ...
    </dependencies>
    ...
</project>
```

（3）**集成 Flyway**

添加 `App.java` 文件，内容如下：

```
import org.flywaydb.core.Flyway;

public class App {
    public static void main(String[] args) {
        // Create the Flyway instance
        Flyway flyway = new Flyway();

        // Point it to the database
        flyway.setDataSource("jdbc:h2:file:./target/foobar", "sa", null);

        // Start the migration
        flyway.migrate();
    }
}
```

（4）**创建第一个 migration**

添加 `src/main/resources/db/migration/V1__Create_person_table.sql` 文件，内容如下：

```
create table PERSON (
    ID int not null,
    NAME varchar(100) not null
);
```

（5）**执行程序**

执行 `App#main`：

运行正常的情况下，应该可以看到如下结果：

```
INFO: Creating schema history table: "PUBLIC"."flyway_schema_history"
INFO: Current version of schema "PUBLIC": << Empty Schema >>
INFO: Migrating schema "PUBLIC" to version 1 - Create person table
INFO: Successfully applied 1 migration to schema "PUBLIC" (execution time 00:00.062s).
```

（6）**添加第二个 migration**

添加 src/main/resources/db/migration/V2\_\_Add\_people.sql 文件，内容如下：

```
insert into PERSON (ID, NAME) values (1, 'Axel');
insert into PERSON (ID, NAME) values (2, 'Mr. Foo');
insert into PERSON (ID, NAME) values (3, 'Ms. Bar');
```

运行正常的情况下，应该可以看到如下结果：

```
INFO: Current version of schema "PUBLIC": 1
INFO: Migrating schema "PUBLIC" to version 2 - Add people
INFO: Successfully applied 1 migration to schema "PUBLIC" (execution time 00:00.090s).
```

### Maven <a href="#maven" id="maven"></a>

与 Java API 方式大体相同，区别在 **集成 Flyway** 步骤：

Maven 方式使用插件来集成 Flyway：

```
<project xmlns="...">
    ...
    <build>
        <plugins>
            <plugin>
                <groupId>org.flywaydb</groupId>
                <artifactId>flyway-maven-plugin</artifactId>
                <version>5.1.4</version>
                <configuration>
                    <url>jdbc:h2:file:./target/foobar</url>
                    <user>sa</user>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>com.h2database</groupId>
                        <artifactId>h2</artifactId>
                        <version>1.4.191</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>
</project>
```

因为用的是插件，所以执行方式不再是运行 Java 类，而是执行 maven 插件：

```
> mvn flyway:migrate
```

> &#x20;参考：[示例源码](https://github.com/dunwu/Database/tree/master/codes/middleware/flyway)

### Gradle <a href="#gradle" id="gradle"></a>

本人不用 Gradle，略。

## 入门篇 <a href="#ru-men-pian" id="ru-men-pian"></a>

### 概念 <a href="#gai-nian" id="gai-nian"></a>

#### Migrations <a href="#migrations" id="migrations"></a>

在 Flyway 中，对于数据库的任何改变都称之为 **Migrations**。

Migrations 可以分为 Versioned migrations 和 Repeatable migrations。

Versioned migrations 有 2 种形式：regular 和 undo。

Versioned migrations 和 Repeatable migrations 都可以使用 SQL 或 JAVA 来编写。

**Versioned migrations**

由一个版本号（version）、一段描述（description）、一个校验（checksum）组成。版本号必须是惟一的。Versioned migrations 只能按顺序执行一次。

一般用于：

* 增删改 tables/indexes/foreign keys/enums/UDTs。
* 引用数据更新
* 用户数据校正

Regular 示例：

```
CREATE TABLE car (
    id INT NOT NULL PRIMARY KEY,
    license_plate VARCHAR NOT NULL,
    color VARCHAR NOT NULL
);

ALTER TABLE owner ADD driver_license_id VARCHAR;

INSERT INTO brand (name) VALUES ('DeLorean');
```

**Undo migrations**

> 注：仅专业版支持

Undo Versioned Migrations 负责撤销 Regular Versioned migrations 的影响。

Undo 示例：

```
DELETE FROM brand WHERE name='DeLorean';

ALTER TABLE owner DROP driver_license_id;

DROP TABLE car;
```

**Repeatable migrations**

由一段描述（description）、一个校验（checksum）组成。Versioned migrations 每次执行后，校验（checksum）会更新。

Repeatable migrations 用于管理可以通过一个文件来维护版本控制的数据库对象。

一般用于：

* 创建（重建）views/procedures/functions/packages 等。
* 大量引用数据重新插入

示例：

```
CREATE OR REPLACE VIEW blue_cars AS
    SELECT id, license_plate FROM cars WHERE color='blue';
```

**基于 SQL 的 migrations**

migrations 最常用的编写形式就是 SQL。

基于 SQL 的 migrations 一般用于：

* DDL 变更（针对 TABLES,VIEWS,TRIGGERS,SEQUENCES 等的 CREATE/ALTER/DROP 操作）
* 简单的引用数据变更（引用数据表中的 CRUD）
* 简单的大量数据变更（常规数据表中的 CRUD）

**命名规则**

为了被 Flyway 自动识别，SQL migrations 的文件命名必须遵循规定的模式：

* **Prefix** - `V` 代表 versioned migrations (可配置), `U` 代表 undo migrations (可配置)、 `R` 代表 repeatable migrations (可配置)
* **Version** - 版本号通过`.`(点)或`_`(下划线)分隔 (repeatable migrations 不需要)
* **Separator** - `__` (两个下划线) (可配置)
* **Description** - 下划线或空格分隔的单词
* **Suffix** - `.sql` (可配置)

**基于 JAVA 的 migrations**

基于 JAVA 的 migrations 适用于使用 SQL 不容易表达的场景：

* BLOB 和 CLOB 变更
* 大量数据的高级变更（重新计算、高级格式变更）

**命名规则**

为了被 Flyway 自动识别，JAVA migrations 的文件命名必须遵循规定的模式：

* **Prefix** - `V` 代表 versioned migrations (可配置), `U` 代表 undo migrations (可配置)、 `R` 代表 repeatable migrations (可配置)
* **Version** - 版本号通过`.`(点)或`_`(下划线)分隔 (repeatable migrations 不需要)
* **Separator** - `__` (两个下划线) (可配置)
* **Description** - 下划线或空格分隔的单词

> &#x20;更多细节请参考：[https://flywaydb.org/documentation/migrations](https://flywaydb.org/documentation/migrations)

#### Callbacks <a href="#callbacks" id="callbacks"></a>

> 注：部分 events 仅专业版支持。

尽管 Migrations 可能已经满足绝大部分场景的需要，但是某些情况下需要你一遍又一遍的执行相同的行为。这可能会重新编译存储过程，更新视图以及许多其他类型的开销。

因为以上原因，Flyway 提供了 Callbacks，用于在 Migrations 生命周期中添加钩子。

Callbacks 可以用 SQL 或 JAVA 来实现。

**SQL Callbacks**

SQL Callbacks 的命名规则为：event 名 + SQL migration。

如： `beforeMigrate.sql`, `beforeEachMigrate.sql`, `afterEachMigrate.sql` 等。

SQL Callbacks 也可以包含描述（description）。这种情况下，SQL Callbacks 文件名 = event 名 + 分隔符 + 描述 + 后缀。例：`beforeRepair__vacuum.sql`

当同一个 event 有多个 SQL callbacks，将按照它们描述（description）的顺序执行。

> **注：** Flyway 也支持你配置的 `sqlMigrationSuffixes`。

**JAVA Callbacks**

> 当 SQL Callbacks 不够方便时，才应考虑 JAVA Callbacks。

JAVA Callbacks 有 3 种形式：

1. **基于 Java 的 Migrations** - 实现 JdbcMigration、SpringJdbcMigration、MigrationInfoProvider、MigrationChecksumProvider、ConfigurationAware、FlywayConfiguration
2. **基于 Java 的 Callbacks** - 实现 org.flywaydb.core.api.callback 接口。
3. **自定义 Migration resolvers 和 executors** - 实现 MigrationResolver、MigrationExecutor、ConfigurationAware、FlywayConfiguration 接口。

> &#x20;更多细节请参考：[https://flywaydb.org/documentation/callbacks](https://flywaydb.org/documentation/callbacks)

#### Error Handlers <a href="#error-handlers" id="error-handlers"></a>

> 注：仅专业版支持。

（略）

#### Dry Runs <a href="#dry-runs" id="dry-runs"></a>

> 注：仅专业版支持。

（略）

### 命令 <a href="#ming-ling" id="ming-ling"></a>

Flyway 的功能主要围绕着 7 个基本命令：[Migrate](https://flywaydb.org/documentation/command/migrate)、[Clean](https://flywaydb.org/documentation/command/clean)、[Info](https://flywaydb.org/documentation/command/info)、[Validate](https://flywaydb.org/documentation/command/validate)、[Undo](https://flywaydb.org/documentation/command/undo)、[Baseline](https://flywaydb.org/documentation/command/baseline) 和 [Repair](https://flywaydb.org/documentation/command/repair)。

注：各命令的使用方法细节请查阅官方文档。

### [支持的数据库](https://dunwu.github.io/db-tutorial/#/sql/middleware/flyway?id=%e6%94%af%e6%8c%81%e7%9a%84%e6%95%b0%e6%8d%ae%e5%ba%93) <a href="#zhi-chi-de-shu-ju-ku" id="zhi-chi-de-shu-ju-ku"></a>

* [Oracle](https://flywaydb.org/documentation/database/oracle)
* [SQL Server](https://flywaydb.org/documentation/database/sqlserver)
* [DB2](https://flywaydb.org/documentation/database/db2)
* [MySQL](https://flywaydb.org/documentation/database/mysql)
* [MariaDB](https://flywaydb.org/documentation/database/mariadb)
* [PostgreSQL](https://flywaydb.org/documentation/database/postgresql)
* [Redshift](https://flywaydb.org/documentation/database/redshift)
* [CockroachDB](https://flywaydb.org/documentation/database/cockroachdb)
* [SAP HANA](https://flywaydb.org/documentation/database/saphana)
* [Sybase ASE](https://flywaydb.org/documentation/database/sybasease)
* [Informix](https://flywaydb.org/documentation/database/informix)
* [H2](https://flywaydb.org/documentation/database/h2)
* [HSQLDB](https://flywaydb.org/documentation/database/hsqldb)
* [Derby](https://flywaydb.org/documentation/database/derby)
* [SQLite](https://flywaydb.org/documentation/database/sqlite)

## 资料 <a href="#zi-liao" id="zi-liao"></a>

\| [Github](https://github.com/flyway/flyway) | [官方文档](https://flywaydb.org/) |
