Flyway基于HighGoDB实现数据库版本管理

1. 我们为什么需要数据库的版本集成?

假设我们有一个名为demo的项目,项目的主要交付物就是提供一块叫做demo soft的代码并连接到名为demo db的数据库。

如果用一个图来表示上述所意的,应该如下:

image

现在我们有了代码和数据库,这也是大多数初创项目所需的所有内容了。

但是随着项目的进展,这种简单的开发模式马上会演变成如下的这种样子:

image

现在我们不止一个环境,而是有了开发、测试、灰度、生产等,由于环境数量的增加,给我们带来了更多的挑战。

git、svn等版本管理工具功能强大,可以很好的帮助我们管理代码。

但是,我们的在数据库的版本管理方面做的不那么的好。很多项目还是依靠dba或是scm手动的执行sql脚本,而有的时候,为了临时的解决某些问题,会在这个或是那个环境手动的执行某些脚本,如此这样就会产生很多的问题。比如我如何知道某台机器上面的数据库处于什么状态。sql脚本文件是执行了,还是没有执行?在生产环境执行的脚本文件随后在其他的环境有执行吗?如何启动一台全新的数据库实例,而与其他的数据库实例保持一致?

数据库的持续集成是解决这一混乱问题的好方法,它具有如下优点

  1. 重头重新的创建数据库
  2. 清楚的知道你的数据库现在处于什么样的状态
  3. 以确定的方式从当前版本的数据库集成到新的版本

2.Flyway的主要特征

Text
普通 SQL:纯 SQL 脚本(包括占位符替换)没有专有的XML格式,没有锁定
无限制:使用 Java 代码来进行一些高级数据操作
零依赖:只需运行在 Java6(及以上)和数据库所需的 JDBC 驱动
约定优于配置:迁移时,自动查找系统文件和类路径中的 SQL 文件或 Java 类
高可靠性:在集群环境下进行数据库升级是安全可靠的
云支持:完全支持 Microsoft SQL Azure, Google Cloud SQL & App Engine、Heroku Postgres 和 Amazon RDS
自动迁移:使用 Flyway 提供的 API,让应用启动和迁移同时工作
快速失败:损坏的数据库或失败的迁移可以防止应用程序启动
数据库清理:在一个数据库中删除所有的表、视图、触发器,而不是删除数据库本身

3.Flyway的工作原理

假设一个最简单的场景,我们要在一个空的数据库上面应用flyway。

image

flyway首先在数据库上面找一个默认的表名为flyway_schema_history的历史记录表,由于此时,数据库为空,所以flyway会创建一个表名为flyway_schema_history的空表。

image

然后flyway会使用此表来跟踪和记录数据库的日志状态。

创建了flyway_schema_history之后,flyway马上会扫描文件系统或是web应用下的classpath路径下的sql文件(如下的介绍默认使用sql文件做为migrations文件)或是java文件为migration做准备。

找到migrations文件之后,会按照version number升序排列,然后依次的执行migrations文件。

image

每执行一次migrations文件,flyway_schema_history表的内容相应的就发生变化。

image

如果数据库已经有了flyway_schema_history表,且其中记录了相应的版本信息,我们现在看一下flyway_schema_history是如何做版本升级的。flyway马上会扫描文件系统或是web应用下的classpath路径下的migrations文件为migration做准备。如果扫描到的migrations文件的版本号小于或是等于当前flyway_schema_history记录的标本号,这些migrations文件会被忽略。而剩下的migrations文件就是准备执行的migrations文件。

image

这些剩余的migrations文件会按照version number升序排列并执行,flyway_schema_history也会相应的记录版本更新的情况。

image

所以如果你想升级数据库的版本信息,不管是DDL或是DML语句,只要生成一个migrations文件,并将其version number大于当前的flyway_schema_history记录的当前版本信息即可,当下次flyway执行的时候,就会找到新增的这些migrations文件,并执行和记录新的版本信息。通过这种方式,可以很方便的将相关的升级脚本与代码一同的发布。

4.Flyway配置属性详解

Text
Url:连接数据库的jdbc的url
Driver:连接数据库的jdbc驱动的class全名,默认为空,flyway会根据url⾃动查找匹配的驱动
User:连接数据库的⽤户名
Password:连接数据库的⽤户名对应的密码
connectRetries:连接数据库的最⼤的重试次数,每次尝试失败后,flyway会等待⼀秒然后继续重试到最⼤次数为⽌(不设置,就不重试,快速的失败)
initSql:连接上数据库之后的初始化sql
defaultSchema:默认的schema,⼤⼩写敏感,是flyway在执⾏过程中默认的schema,flyway_schema_history包含在这个schema⾥⾯,flyway的6.1版本之后,如果没有指定schema,那么默认使⽤schemas属性配置的第⼀个schema
Schemas:使⽤逗号分隔,⼤⼩写敏感,除⾮配置的第⼀个schema已经存在,不然会创建所有的schema,所有的schema会按照顺序做clean操作
Table:默认名flyway_schema_history,如果在schemas配置了多个schema,那么flyway_schema_history表在第⼀个schema⾥⾯
Tablespace:创建flyway_schema_history的表空间,此设置仅与⽀持表空间概念的数据库相关。对其它数据库不起作⽤
Locations:locations使⽤逗号分隔,会对其指定的location进⾏递归查找,location的类型依配置的location的前缀⽽定,没有前缀的location或是以classpath:标记的location会在classpath上扫描sql或是java⽂件,以filesysytem标记的location会在⽂件系统上递归的查找⾮隐藏的migration⽂件,⽽且可以使⽤通配符
Color:仅仅应⽤于命令⾏使⽤,是否可以对于输出添加颜⾊,默认是auto,可以有always和never其它的两个选项
jarDirs:逗号分隔,驱动⽂件和基于Java的migration⽂件所在⽬录
sqlMigrationPrefix:migration⽂件的前缀,默认为V
undoSqlMigrationPrefix:undo⽂件前缀,默认是U
repeatableSqlMigrationPrefix:repeat⽂件前缀,默认是P
sqlMigrationSeparator:migration⽂件分隔符,默认是__
sqlMigrationSuffixes:migration⽂件后缀,默认是.sql
validateMigrationNaming:是否验证migration⽂件,默认是false,如果migration⽂件名格式不满⾜要求,skip,如果为true,快速失败
Stream:是否对migration⽂件流化处理,⽽不是全部的加载到内存之后再处理(如果migratio⽂件很⼤,如1GB,⼀般也不会遇到这样的情况)
Batch:是否对migration⽂件进⾏批处理,可以节约带宽,对于处理⼤的migration⽂件⽽⾔
Encoding:migration⽂件编码
placeholderReplacement:是否进⾏占位符替换,默认为true
placeholderPrefix:占位符前缀,默认为${
placeholderSuffix:占位符后缀,默认为}
Resolvers:migration⽂件解析器,逗号分隔的全class⽂件名,相当于可以扩展内置的resolvers解析器
skipDefaultResolvers:是否skip内置的解析器只使⽤定制化的解析器,默认是false
callbacks:逗号分隔的class⽂件名,在flyway⽣命周期内被回调引⽤
skipDefaultCallbacks:是否skip内置的callbacks,默认false
target:migration时数据库的版本,最好使⽤默认值
outOfOrder:是否可以⽆序执⾏,维持默认即可
ignoreMissingMigrations:忽略丢失的migration⽂件,默认是false,针对⽼的系统可以进⾏

5.Flyway命令

Text
Migrate:迁移操作,对于每次数据库进行版本迁移的操作。将sql脚本中的内容进行执行记录	
Baseline:初始化 schema_version 表,并插入一条原始 version = 1 。实现在非空数据库新建MetaData表,并把Migrations应用到该数据库;也可以在已有表结构的数据库中实现添加Metadata表。
Clean:清除掉对应数据库Schema中所有的对象,包括表结构,视图,存储过程等,clean操作在dev 和 test阶段很好用,但在生产环境务必禁用。
Info:用于打印所有的Migrations的详细和状态信息,也是通过MetaData和Migrations完成的,可以快速定位当前的数据库版本。
Repair:repair操作能够修复metaData表,该操作在metadata出现错误时很有用。
Undo:撤销操作,社区版不支持。
Validate:验证已经apply的Migrations是否有变更,默认开启的,原理是对比MetaData表与本地Migrations的checkNum值,如果值相同则验证通过,否则失败。

6.Migration

Flyway将每一个数据库脚本称之为migrations,flyway支持三种类型的migration:

  1. Versioned migrations:最常用的migration,可以简单的理解为数据库升级脚本
  2. Undo migrations:数据库版本回退脚本,需要Pro版本,忽略,而且使用过程存在较大风险,undo操作目前只能通过plugin或者command-line来执行
  3. Repeatable migrations:可重复执行的migration,例如create or replace脚本,当脚本checksums改变时会重新执行

每个migration支持两种编写方式:

  1. Java:通过实现 org.flywaydb.core.api.migration.jdbc.JdbcMigratio 接口来创建一个Java migration,也就是通过JDBC来执行SQL,对于类是CLOB或者BOLB这种不方便在SQL中实现的脚本比较有用,例如:
package db.migration;
import org.flywaydb.core.api.migration.jdbc.JdbcMigration;
import java.sql.Connection;
import java.sql.PreparedStatement;
public class V1_2__Another_user implements JdbcMigration {
public void migrate(Connection connection) throws Exception {
PreparedStatement statement = connection.prepareStatement("INSERT INTO test_user (name) VALUES ('Obelix')");
try {
statement.execute();
} finally {
statement.close();
}
}
}
  1. SQL:简单的SQL脚本文件,例如:
Text
CREATE TABLE test_user (
name VARCHAR(25) NOT NULL,
PRIMARY KEY(name)
);
INSERT INTO ${tableName} (name) VALUES ('Mr. T');

所有的migration都需要遵守命名规范:

image

确保版本号唯一,flyway按照版本号顺序执行。repeatable没有版本号,因为repeatable migrations会在内容改变时重复执行。

7.基于spring boot的flyway实战

  1. 在pom文件中引入依赖
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>6.0.8</version>
</dependency>
  1. application.yml配置

image

  1. 在resources的db/migration目录下面添加sql脚本

image

  1. 启动项目,查看控制台输出

image

8.Flyway jar包引用注意事项

报错:{conn-10005, pstmt-20009} execute error. SET ROLE ‘sysdba’

企业版数据库不会报错,安全版数据库报 set role 无权限错误。

解决方案(两种方案任选其一):

  1. 关闭安全版数据库三权
  2. 更改flyway源码
    下载flayway源码,修改类文件org.flywaydb.core.internal.database.postgresql.PostgreSQLConnection.java
protected void doRestoreOriginalState() throws SQLException {
#添加下面一行代码
this.jdbcTemplate.execute("SET application_name to securedump");
this.jdbcTemplate.execute("SET ROLE '" + this.originalRole + "'");
}

重新打包,替换项目中引用的flayway原有jar包。