一、简单介绍
Flyway 是一款开源的数据库版本管理工具。它可以很方便的在命令行中使用,或者在Java应用程序中引入,用于管理我们的数据库版本。
在项目或产品中,很难一开始就把业务理清楚,把数据库表设计好,因此数据表也会在迭代周期不断迭代。在Java应用程序中使用Flyway,能快速有效地用于迭代数据库表结构,并保证部署到测试环境或生产环境时,数据表都是保持一致的。
二、为什么要使用Flyway
在多人开发的项目中,我们都习惯了使用SVN或者Git来对代码做版本控制,主要的目的就是为了解决多人开发代码冲突和版本回退的问题。
其实,数据库的变更也需要版本控制,在日常开发中,我们经常会遇到下面的问题:
- 自己写的SQL忘了在所有环境执行。
- 别人写的SQL我们不能确定是否都在所有环境执行过了。
- 有人修改了已经执行过的SQL,期望再次执行。
- 需要新增环境做数据迁移。
- 每次发版需要手动控制先发DB版本,再发布应用版本。
- 其它场景。
有了flyway,这些问题都能得到很好的解决。
三、flyway是如何工作的
flyway工作流程如下:
项目启动,应用程序完成数据库连接池的建立后,Flyway自动运行。
初次使用时,flyway会创建一个 flyway_schema_history 表,用于记录sql执行记录。
Flyway会扫描项目指定路径下(默认是 classpath:db/migration )的所有sql脚本,与 flyway_schema_history 表脚本记录进行比对。如果数据库记录执行过的脚本记录,与项目中的sql脚本不一致,Flyway会报错并停止项目执行。
如果校验通过,则根据表中的sql记录最大版本号,忽略所有版本号不大于该版本的脚本。再按照版本号从小到大,逐个执行其余脚本。并非真正忽略,而是会校验checksum值是否一致,以此来保证历史版本文件未被篡改。
最简单的理解方式是:简单我们在一个空数据库上部署集成了Flyway的应用:
Flyway将在这个空数据中创建一张表,用于记录migration的执行情况,表名称默认为:flyway_schema_history,老版本的表名称:schema_version
紧接着,Flyway根据表中的记录决定是否执行应用程序包中提供的migration
最后将执行结果写入flyway_schema_histor并校验执行结果
下次版本迭代时,提供新的migration,会根据flyway_schema_histor的记录执行新migration
四、测试V
引入依赖
- springboot主依赖版本:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
- 数据库连接、flyway依赖:
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>5.2.4</version>
</dependency>
</dependencies>
【额外注意:】必须增加数据库连接pom依赖!
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
否则会出现项目启动成功,但不会自动生成表的问题
配置数据库连接
server:
port: 80
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/flyway?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
driver-class-name: com.mysql.cj.jdbc.Driver # mysql8的驱动,注意时区,如果是使用 MySQL 8 之前的驱动,可以是 com.mysql.jdbc.Driver
flyway:
locations: classpath:db/migration # 默认配置
enabled: true # 默认
baseline-on-migrate: true
clean-on-validation-error: false
启动类
package com.xxxx.lln;
import org.springframework.boot.SpringApplication;
/**
* 启动类
*
* @author : li.linnan
* @create : 2023/2/27
*/
@org.springframework.boot.autoconfigure.SpringBootApplication
public class SpringBootApplication {
public static void main(String[] args) {
try {
SpringApplication.run(SpringBootApplication.class,args);
}catch (Exception e){
System.out.println(e);
}
}
}
添加脚本
首先需要在classpath目录下,创建一个名为db/migration的文件夹。
然后向该文件夹内,增加SQL脚本。编写SQL脚本需要注意以下几点:
1、仅需要执行一次的脚本,以V开头,后面跟上0~9的数字组合,数字之间可以使用.或者_进行分割。然后再以两个下划线 __进行分割,其后跟上文件名称,最后以.sql结尾。
如: V1__create_user_ddl.sql、V2__create_user.sql。
2、需要重复执行的SQL,则需要以R开头。后面再以两个下划线分割,其后跟文件名称,最后以.sql结尾。
如:R__truncate_user_dml.sql
编写如下所示的测试脚本V1.0.0__Create_user_manage.sql信息,如下所示:
CREATE TABLE IF NOT EXISTS `USER`(
`USER_ID` INT(11) NOT NULL AUTO_INCREMENT,
`USER_NAME` VARCHAR(100) NOT NULL COMMENT '用户姓名',
`AGE` INT(3) NOT NULL COMMENT '年龄',
`CREATED_TIME` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`CREATED_BY` varchar(100) NOT NULL DEFAULT 'UNKNOWN',
`UPDATED_TIME` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`UPDATED_BY` varchar(100) NOT NULL DEFAULT 'UNKNOWN',
PRIMARY KEY (`USER_ID`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
项目结构概览
项目启动,观察日志和数据库结果
为什么会出现两张表?
flyway_schema_history
是一个flyway管理各个版本关系的表,主要用于维护和管理开发者本地的脚本版本信息。
另一张表,则是刚刚的SQL脚本执行结果表,如下所示:
验证V只能执行一次
向数据表里再加一个字段
CREATE TABLE IF NOT EXISTS `USER`(
`USER_ID` INT(11) NOT NULL AUTO_INCREMENT,
`USER_NAME` VARCHAR(100) NOT NULL COMMENT '用户姓名',
`AGE` INT(3) NOT NULL COMMENT '年龄',
`CREATED_TIME` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`CREATED_BY` varchar(100) NOT NULL DEFAULT 'UNKNOWN',
`UPDATED_TIME` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`UPDATED_BY` varchar(100) NOT NULL DEFAULT 'UNKNOWN',
PRIMARY KEY (`USER_ID`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `user` add column USER_POSITION varchar(11) default null comment '位置';
重启项目,观察现象:
从控制台日志信息可以看出:
V开头的脚本文件,在项目启动时,会去flyway_schema_history表中匹配判断!
如果文本信息不匹配,则直接报错!!!!
强制修改初始化脚本
1.把数据库和脚本都同步修改掉
2.用最新的checksum,即用上图中的1642862594
替换掉数据表flyway_schema_history中的checksum
基准版本
这个暂时没搞明白
如果有多个版本的文件 例如:1.0.0 ;1.0.1;1.0.2
flyway 会根据版本号顺序依次执行
可能在第一次使用的时候,不想再重新执行一遍之前的sql,就需要调整 flyway自动执行的基准 版本 请修改配置 baseline-version
不想再重新执行一遍之前的sql,就需要调整 flyway自动执行的基准版本
版本号>基准版本号/当前版本号 才会执行
五、测试R
在classpath文件夹中,新增一个R__add_user_info.sql
的脚本信息,其中SQL脚本如下所示:
insert into `user`(user_name,age) values('lln',35);
重启项目,观察结果
【注意】这里的 R 重复执行脚本,并不是说是启动项目后不断执行!
而是,每次启动项目,都会重新校验对应的 R__add_user_info.sql 内容是否变更,如果变更则重新执行。
修改R__add_user_info.sql中的信息,重新启动项目:
-- insert into `user`(user_name,age) values('lln',35);
insert into `user`(user_name,age) values('xiangjiao',22);
变更数据库字段
正常开发中,字段信息的变更,往往不是很常见,这个要看需求。
如果因为需求的出现,导致需要在数据库表中增加或修改字段信息,此时flyway能轻松胜任。
之前的SQL脚本中,只设定有用户名、性别等信息,但如果想增加一个密码字段,此时则需要新增一个脚本。
V1.0.3__add_user_pwd.sql,其中脚本内容如下所示:
ALTER TABLE `user` add column user_pwd varchar(11) default null comment '密码' after `age`;
Flyway历史记录表
Flyway通过创建flyway_history_schema来记录迁移,表中的主要字段如下:
字段名 | 描述 |
---|---|
installed_rank | 执行排名,值越低优先级越高,即版本越低越先执行 |
version | 执行的版本 |
description | 版本描述 |
type | 脚本类型 |
script | 脚本文件名 |
checksum | 校验和,用于检测是由有更改 |
installed_by | 脚本执行人员 |
installed_on | 执行时间 |
execution_time | 执行脚本所耗时间 |
success | 是否执行成功(0-失败,1成功) |
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