最近的公司项目需求需要在现有的项目中集成spring-security,鉴于对spring security的知识较为零散,虽然之前集中学习了一段时间的spring security,但没有实践还需要多学习。今天在查看spring security资料的时候接触到了flyway这个东西,感觉用起来还是挺方便,打算学习学习也一起集成到项目中。
我们为什么需要数据库迁移管理
比如第一个版本的产品只包含了最基本的功能,而第二版本就需要增加评论功能,这就涉及到数据结构的修改(包括创建新表,修改旧表的列,增加已有表的列等等)。直接进入产品数据库修改数据库并不适合快速的开发节奏,不仅仅不安全,更多的情况下数据库可能并不对外或者并不适合对外直接暴露连接,比如PAAS平台的数据库以服务的形式直接提供。
对比代码管理的一些实践,很明显在数据库方面做的还欠缺很多。比如代码管理中我们有
- 版本管理(svn,git等等)
- 持续集成技术
- 良好的发布工具和流程
而在数据库方面会遇到很多问题
- 某台数据库现在是什么状态
- 修改变更的脚本是否已经应用
- 对于生产环境的紧急修复有没有被应用在测试环境
- 如何创建一个新的数据库实例
数据库迁移工具可以很好的管理这些问题,并提供了以下特性
- 从迁移脚本中创建新的数据库
- 检查数据库状态
- 从一个版本快速到达另外一个版本
什么是 Flyway
我们在做开发时,由于项目需求的变化,或者前期设计缺陷,导致在后期需要修改数据库,这应该是一个比较常见的事情,如果项目还没上线,你可能把表删除了重新创建,但是如果项目已经上线了,就不能这样简单粗暴了,我们需要通过 SQL 脚本在已有数据表的基础上进行升级。
目前 Java 这块,想要对数据库的版本进行管理主要有两个工具:
Flyway,Liquibase两个工具各有千秋,但是核心功能都是数据库的版本管理,这里主要来看 Flyway。就像我们使用 Git 来管理代码版本一样,Flyway 可以用来管理数据库版本。
Flyway 的特点
Flyway 大受欢迎是因为它具有以下优点:
- 简单 非常容易安装和学习,同时迁移的方式也很容易被开发者接受。
- 专一 Flyway 专注于搞数据库迁移、版本控制而并没有其它副作用。
- 强大 专为连续交付而设计。让Flyway在应用程序启动时迁移数据库。
Flyway 的工作机制
Flyway 需要在 DB
中先创建一个 metadata
表 (缺省表名为 flyway_schema_history
), 在该表中保存着每次 migration
(迁移)的记录, 记录包含 migration
脚本的版本号和 SQL 脚本的 checksum
值。下图表示了多个数据库版本。
对应的 metadata
表记录:
installed_rank | version | description | type | script | checksum | installed_by | installed_on | execution_time | success |
---|---|---|---|---|---|---|---|---|---|
1 | 1 | Initial Setup | SQL | V1__Initial_Setup.sql | 1996767037 | axel | 2016-02-04 22:23:00.0 | 546 | true |
2 | 2 | First Changes | SQL | V2__First_Changes.sql | 1279644856 | axel | 2016-02-06 09:18:00.0 | 127 | true |
Flyway 的规则
Flyway 是如何比较两个 SQL 文件的先后顺序呢?它采用 采用左对齐原则, 缺位用 0 代替 。举几个例子:
1.0.1.1 比 1.0.1 版本高。
1.0.10 比 1.0.9.4 版本高。
1.0.10 和 1.0.010 版本号一样高, 每个版本号部分的前导 0 会被忽略。
Flyway 将 SQL 文件分为 Versioned 、Repeatable 和 Undo 三种:
- Versioned 用于版本升级, 每个版本有唯一的版本号并只能执行一次.
- Repeatable 可重复执行, 当 Flyway检测到 Repeatable 类型的 SQL 脚本的
checksum
有变动, Flyway 就会重新应用该脚本. 它并不用于版本更新, 这类的migration
总是在 Versioned 执行之后才被执行。 - Undo 用于撤销具有相同版本的版本化迁移带来的影响。但是该回滚过于粗暴,过于机械化,一般不推荐使用。一般建议使用 Versioned 模式来解决。
这三种的命名规则如下图:
- Prefix 可配置,前缀标识,默认值
V
表示 Versioned,R
表示 Repeatable,U
表示 Undo - Version 标识版本号, 由一个或多个数字构成, 数字之间的分隔符可用点
.
或下划线_
- Separator 可配置, 用于分隔版本标识与描述信息, 默认为两个下划线
__
- Description 描述信息, 文字之间可以用下划线
_
或空格 ``分隔 - Suffix 可配置, 后续标识, 默认为
.sql
集成到项目中
如果是在一个全新的项目中使用 Flyway,那么在新建一个 Spring Boot 项目时,就有 Flyway 的选项,如下图:
项目创建成功后,resources目录下也会多出来一个db/migration目录,这个目录用来存放数据库脚本,如下:
注意
这个如果创建项目时就选择了 Flyway 依赖,就会有这个目录。现在我要在已经做好的项目中加入 Flyway,这个目录就需要我手动创建了。
- 首先在pom中添加 flyway 依赖:
|
|
gradle
|
|
- 然后在项目的 resources 目录下,手动创建 db/migration 目录
- 然后在该目录下创建数据库脚本,数据库脚本的命名方式如下:
V
例如我这里创建我的第一个数据库脚本,取名为 V1.20.1__gp.sql。
可以不用添加额外配置,大家只需要在本地 MySQL 中创建一个空的 gp数据库即可,然后直接启动项目,项目启动成功后,我们查看启动日志:
从这段启动日志中,我们可以看到 Flyway 的执行信息,数据库脚本的执行执行,同时这里还说了,Flyway 还给创建了一个 flyway_schema_history 表,这个表用来记录数据库的更新历史。
这个时候,打开本地数据库,我们发现 gp库中该有的表都有了。同时还发现了 flyway_schema_history 表,如下:
有了这条记录,下次再启动项目,这个sql脚本文件就不会执行了,因为系统知道这个脚本已经执行过了,如果你还想让脚本再执行一遍,需要手动删除 flyway_schema_history 表中的对应记录,那么项目启动时,这个脚本就会被执行了。
3.执行细节
我们在定义脚本的时候,除了 V 字开头的脚本之外,还有一种 R 字开头的脚本,V 字开头的脚本只会执行一次,而 R 字开头的脚本,只要脚本内容发生了变化,启动时候就会执行。使用了 Flyway 之后,如果再想进行数据库版本升级,就不用改以前的数据库脚本了,直接创建新的数据库脚本,项目在启动时检测了有新的更高版本的脚本,就会自动执行,这样,在和其他同事配合工作时,也会方便很多。因为正常我们都是从 Git 上拉代码下来,不拉数据库脚本,这样要是有人更新了数据库,其他同事不一定能够收到最新的通知,使用了 Flyway 就可以有效避免这个问题了。所有的脚本,一旦执行了,就会在 flyway_schema_history 表中有记录,如果你不小心搞错了,可以手动从 flyway_schema_history 表中删除记录,然后修改 SQL 脚本后再重新启动(生产环境不建议)。
4.其他配置
在 Spring Boot 中,关于 Flyway 也有不少配置,这些配置都在 application.properties 中进行配置,常用的几个来和大家说下:
spring.flyway.enabled:是否开启 flyway,默认就是开启的
spring.flyway.encoding:flyway 字符编码
spring.flyway.locations:sql 脚本的目录,默认是 classpath:db/migration,如果有多个,用 , 隔开
spring.flyway.clean-disabled:这个属性非常关键,它表示是否要清除已有库下的表,如果执行的脚本是 V1__xxx.sql,那么会先清除已有库下的表,然后再执行脚本,这在开发环境下还挺方便,但是在生产环境下就要命了,而且它默认就是要清除,生产环境一定要自己配置设置为 true。
spring.flyway.table:配置数据库信息表的名称,默认是 flyway_schema_history。
|
|
Flyway 最佳实践
通过上面的介绍相信你很快就会使用 Flyway 进行数据库版本控制了。这里总结了一些在实际开发中的使用经验:
- 生产务必禁
spring.flyway.cleanDisabled=false
。 - 尽量避免使用 Undo 模式。
- 开发版本号尽量根据团队来进行多层次的命名避免混乱。比如
V1.0.1__ProjectName_{Feature|fix}_Developer_Description.sql
,这种命名同时也可以获取更多脚本的开发者和相关功能的信息。 spring.flyway.outOfOrder
取值 生产上使用false
,开发中使用true
。- 多个系统公用一个 数据库
schema
时配置spring.flyway.table
为不同的系统设置不同的metadata
表名而不使用缺省值flyway_schema_history
。
附录: Flyway配置详解
|
|