内容纲要

环境

  • Java
  • Spring框架
  • Gradle
  • JDBC
  • Mysql

概述

Flyway是一个数据库版本控制工具,用于在项目中管理数据库需要执行的sql脚本。在很多项目中都会有多个环境,包括测试以及生产等,如果是手动执行sql语句就需要在上线后在生产环境中执行很多sql以确保多环境的一致性。当环境更多时,手动执行sql不仅费时费力还很容易出现遗漏和错误,造成数据库结构在多个环境中不一致的问题。在这时我们就需要使用Flyway对数据库版本进行控制,以确保每个环境都会运行一套sql。

具体参考官方文档:https://documentation.red-gate.com/flyway

使用示例

  1. 引入依赖
implementation 'org.flywaydb:flyway-core'
  1. 配置flyway

在项目的application.yml中配置如下:

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/order?useUnicode=true&useSSL=false
    username: root
    password: ****
  jpa:
    database: mysql
  flyway:
    enabled: true
    locations: [ "classpath:/db/migration" ]

其中spring.datasource是用于配置数据源,我配置的是本地mysql作为该demo的数据库连接。其中flyway的配置enabled表示是否启用flyway功能,这里设置为true也就是开启。flyway.locations设置的是flyway脚本的存放目录,可以看一下我的项目目录。使用这个配置可以支持将多个目录的脚本合并后执行。

  1. 按照时间撰写需要执行的sql脚本

在上面的截图中我已经写了3条sql,推荐项目内指定一个文件名规范,在这里我使用的方式是【Vyyyy_MM_dd_HH_mm__description】这种时间+文件描述的命名方式。这样命名后,后添加的文件就会在目录的最下面,该目录中的sql会按照从上至下的顺序(也等同于时间顺序)运行。

  1. 运行程序查看结果

在上述示例中我增加了两个数据表:order和payment,在运行FlywayApplication后本地mysql的order数据库中就多了order和payment的表:

运行逻辑

通过上述的示例,我们看一下Flyway的运行规则,也解释一下为什么sql文件名需要按照时间命名。

先看一下我写的三个sql文件的内容:

create table `order`
(
    id int auto_increment,
    customer_name varchar(64) not null,
    amount decimal(10,2) not null,
    constraint order_pk
        primary key (id)
);
create table payment
(
    id int auto_increment,
    order_id int not null,
    pay_time datetime default now() not null,
    constraint payment_pk
        primary key (id)
);
alter table `order`
    add created_time datetime not null;

这三个sql分别是:创建order表、创建payment表、在order表中增加字段。在执行了3个sql后我确实能够在order数据库中看到新增的表结构,与此同时还有一个名为flyway_schema_history的表,我们就从这个表开始看。

flyway_schema_history

参考示例的3个sql,在Flyway执行后表中会多出这样3条数据,分别对应3个sql文件。

  • version:该sql的版本号,在该示例中对应了文件名中的时间。
  • description:该sql的描述,在该示例中对应了文件名中的描述。
  • script:该sql执行对应的文件名。
  • checksum:Flyway会将sql的内容生成checksum并记录下来,当每一次Flyway运行时都会将已运行过的sql脚本的版本号和checksum做比较,如果checksum有修改(也就是sql内容有修改)的话会在运行中报错。也就是说,当一个sql文件成功在Flyway中运行后就不能再做修改,若需要对数据表做任何修改需要再新增一个sql文件用以修改。
  • success:执行的结果(成功/失败)。

Flyway工作流程

  1. 初次使用时创建flyway_schema_history表,用于记录所有sql的运行记录和执行状态。
  2. 从上至下扫描flyway目录下的所有脚本,与flyway_schema_history中的记录做对比,主要是对比version和checksum。若已经执行过的记录与现有目录下的文件checksum不一致,或者在目录中间插入了新增的sql脚本,则Flyway运行就会报错。比如:
    • 将上述3个文件中的某处进行修改
    • 在flyway目录下插入一个version为2024.01.22.20.49的脚本,它的顺序就会变为3,而本来第三个执行的脚本顺序会被挤到4
  3. 忽略所有曾执行过的脚本,直接从记录过的最大version的下一个脚本开始执行。执行按照version从小到大的顺序运行脚本。

团队管理

因为Flyway的checksum机制和version的定义,使用起来会对团队有一定的要求,至少整个开发团队需要制定一个统一的规范。

  • Flyway脚本的命名方式,必须按照【V+version__description】的方式统一,以确保version生成后一定会按照从上至下的方式排列。
  • 不允许团队中的成员修改已经存在的脚本。
  • 添加新的脚本需要验证成功后再提交至环境中。否则会新增一条success=0的记录,而这个脚本一旦修改就会使得checksum的验证无法通过。
  • 在添加Flyway后尽快提交代码。在人员较多的团队中会出现一个问题,当成员A按照现在的时间添加了一条脚本后,成员B也按照时间添加了一个脚本。也就是说成员A的version小于成员B的version。一旦成员A的代码提交在成员B之后,version的顺序就被打乱了,Flyway会认为成员A在已存在的脚本中插入了一条。

既然引入Flyway造成了如此之多的限制,我们又为什么要在项目中使用呢?其实恰巧是因为诸多限制和规范,从团队管理和项目管理的角度才应该引入Flyway。

  1. 对于数据库的操作很难追溯,但代码提交会留下git记录,这样就可以知道数据库每一次修改的执行人。
  2. 每一次对于数据库的修改都会留下记录,在大型项目运行很久后我们依然可以通过脚本了解到数据库结构是如何演变成现在的模样的。
  3. 促使团队达成一定的代码规范。
  4. 对于数据库脚本,团队中的任何一个人都不能修改其他人的脚本,以防对现有功能造成一定的影响。
  5. 推动程序员进行小步提交。既然存在多人在短时间内新增脚本产生的version问题,那么就需要团队成员在添加脚本后尽快提交。这样小步提交不仅减少了Flyway报错的可能性,也减少了代码冲突的可能性,避免团队成员在解决冲突中浪费时间。
最后修改日期: 2024年1月22日

留言

撰写回覆或留言

发布留言必须填写的电子邮件地址不会公开。