1. 引言
想到要管理数据库的版本,是在实际产品中遇到问题后想到的一种解决方案,当时各个环境的数据库乱作一团,没有任何一个人(开发、测试、维护人员)能够讲清楚当前环境下的数据库是哪个版本,与哪个版本的应用相匹配,如何升级到与新版本的应用相匹配。
想到管理数据库版本时,先是心底形成了一个初步的解决方案,大致是通过数据库中的某张表来记录数据库表结构的历次更新与对应版本,在每次数据库表结构调整时除了提供库表更新sql,还必须提供更新记录与对应版本的sql,以帮助维护数据库版本信息,并在遇到问题时提供相关的排查依据。
后来据此思路在网络上搜索了一把,结果搜到Liquibase(另一款开源数据库版本管理工具)。在学习了解Liquibase的时候,经高手介绍又了解到了Flyway这个项目的存在。经过一番了解,最后我们选择了Flyway,主要原因是Flyway支持sql脚本,而Liquibase只支持XML方式的数据库表结构定义,虽然在新的版本中号称在XML的数据库表结构定义方式中支持了sql脚本。
虽然Flyway的中文文档近乎为零,英文文档也凤毛麟角,但它却是我们最理想的数据库版本管理工具,它不但支持sql脚本,还支持Java代码直接操作数据库(在版本升级时做数据迁移相当有用),有Maven插件,支持命令行(我们的平台数据库有部分由C语言项目管理),而且在Spring框架的配合下,很容易就能实现应用启动时自动检查并升级数据库的功能。
2. 什么是Flyway
Flyway是独立于数据库的应用、管理并跟踪数据库变更的数据库版本管理工具。
Flyway的项目主页是http://flywaydb.org/)(最近才迁移到这个主页,之前一直在googlecode下管理http://code.google.com/p/flyway/),在项目的主页上可以看到Flyway与几款主流数据库版本管理工具的特性对比列表。
3. 为什么使用Flyway
3.1.我们遇到的问题
我们遇到的问题(以下内容来自Flyway的一些英文文档,从中抽取出来的我们也遇到的一些主要问题):
- 不同的开发人员在开发产品特性时,都有可能更新数据库(添加新表,新的约束等)。当开发人员完成工作并提交代码时,代码会被合并到主分支并在测试服务器上执行单元测试与集成测试。我们在哪个环节来执行数据库的更新操作呢?由QA部门手工执行sql脚本?或者我们开发一断程序自动执行数据库更新?以什么顺序来执行这些更新脚本?这些问题同样存在于生产环境。
- 我们的产品部署在不同的客户服务器上,以及很多的测试、联调、实验局、销售环境上。不同的客户和测试环境上都部署着不同版本的产品。当他们需要升级他们的产品到新的版本时,我们不仅需要让他们的管理员可以升级产品到新的版本,同时需要保留他们的已有数据。在升级产品的步骤中,我们清楚地知道客户数据库的当前版本,以及需要在该数据库上执行哪些数据库更新脚本,来更新数据库表结构与数据库中已存在的数据。当升级完成时,数据库表结构及数据应当与升级后的产品版本保持一致。
- 有的时候,我们需要通过代码(Java)来维护一些已存在的数据,如通过代码来维护blob类型的用户头像数据。
- 当升级失败时(比如在升级过程中出现网络连接失败),我们应当支持对失败进行修复。
3.2.Flyway的特性
- 自动升级(自动发现更新项):Flyway会将任意版本的数据库升级到最新版本。Flyway可以脱离JVM环境通过命令行执行,可以通过Ant脚本执行,通过Maven脚本执行(这样就可以在集成环境自动执行),并且可以在应用中执行(比如在应用启动时执行)。
- 规约优于配置:Flyway有一套默认的规约,所以不需要修改任何配置就可以正常使用。
- 既支持SQL脚本,又支持Java代码:可以使用SQL脚本执行数据库更新,也可以使用Java代码来进行一些高级数据升级操作。
- 高可靠性:在集群环境下进行数据库升级是安全可靠的。
- 支持清除已存在的库表结构:Flyway可以清除已存在的库表结构,可以从零开始搭建您的库表结构,并管理您的数据库版本升级工作。
- 支持失败修复。新的2.0版本提供了repair功能,用于解决数据库更新操作失败问题。
结合我们遇到的问题,与Flyway所提供的特性,我们认为Flyway是比较适合于我们的一款数据库版本管理工具。
4. 如何使用Flyway
使用Flyway,我们需要准备Flyway将要执行的数据库脚本(Flyway支持sql脚本与java代码,这里认为在Flyway下执行数据库更新操作的java代码也是一种数据库脚本),然后通过Flyway提供的几种不同运行方式来执行这些脚本。(以下配置参数说明基于Flyway 1.7版本,新的2.0版本在配置参数上有不少变动,与下面的介绍会有不少出入,以下说明仅供参考)
4.1.数据库脚本
Flyway的主要任务是管理数据库的版本更新,在Flyway中称每次数据库更新为一个migration,为了更顺口,我们下面称之为数据库脚本。Flyway支持SQL-based migrations和Java-based migrations。
Flyway支持的数据库脚本有sql脚本与java代码,sql脚本即普通的sql脚本,包含创建数据库、表,更新库表结构,数据插入、更新、删除等sql语句,java代码则是通过一个有效的数据源,使用java语言来进行数据库的操作,这里针对的读者是对数据库操作有一定熟悉程度的群体,不再详细讲解如何编写数据库脚本。
Flyway的sql脚本与java代码都遵循以下默认规约:
4.1.1. Flyway默认规约
- SQL脚本文件默认位置是项目的源文件夹下的db/migration 目录。
- Java代码默认位于db.migration 包。
- SQL脚本文件及Java代码类名必须遵循以下命名规则:V[_][__description]。版本号的数字间以小数点(.)或下划线(_)分隔开,版本号与描述间以连续的两个下划线(__)分隔开。如V1_1_0__Update.sql。Java类名规约不允许存在小数点,所以Java类名中版本号的数字间只能以下划线进行分隔。
4.2.Flyway的几种运行方式
本章主要讲解我们常用的三种Flyway的执行方式,Flyway除了提供这三种执行方式外,还提供Ant任务方式的执行方式,有兴趣的同学可以去官方网站获取相关信息,这里不进行描述。
4.2.1. 命令行方式
通过命令行方式运行Flyway,需要下载flyway-commandline版本并解压到本地,然后flyway(Windows下flyway.cmd,Linux下flyway.sh)命令执行Flyway相关操作。
下图是flyway-commandline-1.7解压后的目录结构:
命令行方式的特点与规约
- 无需安装JVM,Maven,Ant
- 默认读取conf/flyway.properties中的配置信息,如果在命令行中指定参数,命令行中指定的参数将覆盖配置文件中的配置
- 还可以通过参数-configFlie=myFlyway.properties来重新指定flyway配置文件,可以通过-configFileEncoding=GBK来指定配置文件的编码格式
- 可以将打包好的java迁移文件放到jars/ 目录下让flyway可以找到并运行
- 数据库驱动包(jar)放到jars/ 目录下
- sql脚本文件放到sql/ 目录中
命令行方式运行的配置及使用方法
- 修改conf/flyway.properties 配置文件
- 拷贝数据库jdbc 驱动jar 到jars/ 目录
- 在sql/ 目录下创建配置好的sql 脚本文件目录路径,如flyway默认的sql 文件路径为db/migration,我们就需要在sql/ 目录下创建/db/migration 目录结构
- 将数据库维护脚本放到创建好的sql 脚本文件目录中(维护脚本文件名需要遵循命名规范)
- 在命令行执行命令(从flyway安装目录开始执行)flyway init(初始化Flyway metadata)、flyway migrate(执行Flyway升级操作)、flyway validate(校验Flyway数据正确性)
4.2.2. Maven插件
配置Maven插件
Xml代码
-
- com.googlecode.flyway
- flyway-maven-plugin
- 1.7
-
-
- mysql
- mysql-connector-java
- ${mysql.connector.version}
-
-
-
- com.mysql.jdbc.Driver
- jdbc:mysql://localhost/flywaydemo?useUnicode=true&characterEncoding=utf-8
- root
-
-
-
- flywaydemo
-
-
-
-
- flyway/migrations
- com.kedacom.flywaydemo.migrations
-
-
- UTF-8
-
- ALL
-
- FAIL
-
-
上面的插件配置包含了几方面的配置信息:
- 声明插件
- 声明数据库驱动的依赖包
- Flyway配置——数据库连接配置
- Flyway配置——Flyway参数与行为配置
执行Maven命令进行Flyway操作(下面列出几种常用的操作)
- mvn flyway:init(初始化Flyway metadata)
- mvn flyway:migrate(执行Flyway升级操作)
- mvn flyway:validate(校验Flyway数据正确性)
4.2.3. 在应用启动时自动运行(结合Spring)
定义在应用启动时自动运行Flyway的Java类,并实现其逻辑代码
Java代码
- public class FlywayMigration {
-
- private DataSource dataSource;
-
- public void setDataSource(DataSource dataSource) {
- this.dataSource = dataSource;
- }
-
- public void migrate() {
- Flyway flyway = new Flyway();
- flyway.setDataSource(dataSource);
-
- flyway.setSchemas("flywaydemo"); // 设置接受flyway进行版本管理的多个数据库
- flyway.setTable("schema_version"); // 设置存放flyway metadata数据的表名
- flyway.setLocations("flyway/migrations", "com.kedacom.flywaydemo.migrations"); // 设置flyway扫描sql升级脚本、java升级脚本的目录路径或包路径
- flyway.setEncoding("UTF-8"); // 设置sql脚本文件的编码
- flyway.setValidationMode(ValidationMode.ALL); // 设置执行migrate操作之前的validation行为
- flyway.setValidationErrorMode(ValidationErrorMode.FAIL); // 设置当validation失败时的系统行为
-
- flyway.migrate();
- }
-
- }
在Spring中根据上面实现的类来定义(实例化)一个bean
Xml代码
-
-
-
从上面的bean定义中我们可以看到,我们为flywayMigration这个bean实例注入了一个数据源,Flyway的所有操作将针对这个数据源进行;同时我们通过init-method属性指定了Spring在实例化该bean以后,主动执行该bean的migrate方法,而该方法内会执行Flyway更新数据库的操作。
至此,我们达到了在应用启动时,Spring实例化上下文的时候,在Spring实例化flywayMigration这个bean的时候,自动执行Flyway更新数据库的操作。
但是,我们还没有达到目的,万一Flyway还在更新数据库,没有完成更新操作之前,应用程序的其他逻辑已经开始使用数据库进行其他操作了,会导致应用程序产生很多bug,甚至根本运行不起来。
要解决这个问题,我们可以利用Spring的bean依赖原理,让关键的数据库操作bean依赖于flywayMigration这个bean,达到在flywayMigration没有实例化完成(数据库更新操作完成)之前,不能进行任何其他数据库相关操作。
利用Spring的bean依赖让flywayMigration优先处理数据库更新操作
Xml代码
-
-
-
-
-
-
-
5. 总结
本篇我们介绍了什么是Flyway,为什么使用Flyway,以及如何使用Flyway,但实际产品/项目中的情况可能更复杂,仅靠对Flyway技术使用上的了解并不能达到我们满意的解决方案,为此我将在下一篇中介绍我们结合项目实际的问题形成的一些基于Flyway的数据库版本管理解决方案。下一篇的内容主要包括:
- 我们的项目中实际是如何使用Flyway的
- 如何在已有的项目中集成Flyway
- 如何在多应用、跨平台、跨语言的环境中使用Flyway