聊一聊Flyway DB Migration

为什么需要 database migration

上学的时候我们可能会遇到一些web相关的大作业或者project,那时候我们一个人或者两三人一组完成全部的前端后端以及数据库,从头到尾只有一个github repo,也不会去根据dev,test,product区分不同的版本。但是当我们面对工业级项目的时候就不是这样了,一方面根据项目的大小会有更多的developer参与其中,另一方面会有CI test,同时还需要有separate的production branch。如果仅对于code本身,git等version control的工具就可以满足需求,但是很多时候我们还需要database,而database的schema甚至其中的test用数据是很难通过git之类寻常的工具来track version的。例如下图的例子:


每次当我们merge代码的时候,我们也希望可以同时将 DB 更新到相应的state。

How Flyway works

Flyway的基本原理是从最初create db开始,用户需要对于每次db schema的更新操作都为之create一个migrate script,这个script一般具有这样都命名规则:V<version_number>__<descritption>.sql,其内容就是由sql写成的对于db操作的内容,例如CREATE TABLE users {...}。而Flyway本身会在用户新建的db中另外create一个table叫做 flyway_schema_history,这个表中会存有db version control所需要的各种meta-data,下面是一个例子:

mysql> select * from flyway_schema_history;
+----------------+---------+-------------+------+------------------+-------------+--------------+---------------------+----------------+---------+
| installed_rank | version | description | type | script           | checksum    | installed_by | installed_on        | execution_time | success |
+----------------+---------+-------------+------+------------------+-------------+--------------+---------------------+----------------+---------+
|              1 | 1       | init        | SQL  | V1__init.sql     |  1952043475 | root         | 2018-03-06 11:25:58 |             16 |       1 |
|              2 | 2       | testdata    | SQL  | V2__testdata.sql | -1926058189 | root         | 2018-03-06 11:25:58 |              6 |       1 |
+----------------+---------+-------------+------+------------------+-------------+--------------+---------------------+----------------+---------+
2 rows in set (0.00 sec)
(例如这里我们有两个migration script)

当运行 flyway migrate 的时候,它会作如下操作:

  1. 检查meta-data table,如果没有会新建一个;
  2. 扫描migration script所在的path;
  3. 对比所有script与meta-data table,如果script的version number比当前version小,则忽略;
  4. 将所有剩余的version number大于当前version的script标记为 pending migrations,按照version number排序;
  5. 依次apply每个script,同时更新meta-data table。

这样,当我们完成了一部分feature的开发,需要check in code的时候,如果我们也更改了db schema,我们就可以将新建的 migration scripts 随代码一同 check in,当其他developer pull了之后,Flyway 就可以根据新添加的 migration scripts 自动将该 developer 的本地 db 也更新到相应的状态。

Sprint Boot Integration

这里提供一个简单的例子来解释如何在SpringBoot中使用Flyway。

  • 1. Use Sprint Boot CLI to generate application

spring init --name=flyway-demo --dependencies=web,mysql,data-jpa,flyway flyway-demo
新建好的application会具有如下目录结构:

  • 2. Configure MySQL and Hibernate

src/main/application.properties中添加如下 properties:

## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.url = jdbc:mysql://localhost:3306/flyway_demo?useSSL=false
spring.datasource.username = root
spring.datasource.password = root

## Hibernate Properties
# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect

## This is important
# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = validate 

需要注意这里的最后一个property ddl-auto = validate,这会令SpringBoot来check当前的DB schema和Java中的entites是否match,而不是根据Java中的entities来修改database schema(vs update)。

  • 3. Create a Flyway migration script

Flyway默认会从classpath:db/migration目录扫描script。

  1. mkdir -p src/main/resources/db/migration
  2. touch src/main/resources/db/migration/V1__init.sql
  3. V1__init.sql中添加如下内容:
     CREATE TABLE users (
      id bigint(20) NOT NULL AUTO_INCREMENT,
      username varchar(100) NOT NULL,
      first_name varchar(50) NOT NULL,
      last_name varchar(50) DEFAULT NULL,
      PRIMARY KEY (id),
      UNIQUE KEY UK_username (username)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
  • 4. Run the application

mvn sprint-boot:run


Flyway 自动扫描 db/migration 目录并找到我们刚刚新建的 V1__init.sql并 执行,db完成更新。

Useful links

2 Likes

Spring boot document for out-of-box flyway integration: https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto-execute-flyway-database-migrations-on-startup