想到要管理数据库的版本,是在实际产品中遇到问题后想到的一种解决方案,当时各个环境的数据库乱作一团,没有任何一个人(开发、测试、维护人员)能够讲清楚当前环境下的数据库是哪个版本,与哪个版本的应用相匹配,如何升级到与新版本的应用相匹配。
想到管理数据库版本时,先是心底形成了一个初步的解决方案,大致是通过数据库中的某张表来记录数据库表结构的历次更新与对应版本,在每次数据库表结构调整时除了提供库表更新sql ,还必须提供更新记录与对应版本的sql ,以帮助维护数据库版本信息,并在遇到问题时提供相关的排查依据。
后来据此思路在网络上搜索了一把,结果搜到Liquibase (另一款开源数据库版本管理工具)。在学习了解Liquibase 的时候,经高手介绍又了解到了Flyway 这个项目的存在。经过一番了解,最后我们选择了Flyway ,主要原因是Flyway 支持sql 脚本,而Liquibase 只支持XML 方式的数据库表结构定义,虽然在新的版本中号称在XML 的数据库表结构定义方式中支持了sql 脚本。
虽然Flyway 的中文文档近乎为零,英文文档也凤毛麟角,但它却是我们最理想的数据库版本管理工具,它不但支持sql 脚本,还支持Java 代码直接操作数据库(在版本升级时做数据迁移相当有用),有Maven 插件,支持命令行(我们的平台数据库有部分由C 语言项目管理),而且在Spring 框架的配合下,很容易就能实现应用启动时自动检查并升级数据库的功能。
Flyway 是独立于数据库的应用、管理并跟踪数据库变更的数据库版本管理工具。
Flyway 的项目主页是 http://flywaydb.org/ )(最近才迁移到这个主页,之前一直在googlecode 下管理 http://code.google.com/p/flyway/ ),在项目的主页上可以看到Flyway 与几款主流数据库版本管理工具的特性对比列表。
我们遇到的问题(以下内容来自Flyway 的一些英文文档,从中抽取出来的我们也遇到的一些主要问题):
结合我们遇到的问题,与Flyway 所提供的特性,我们认为Flyway 是比较适合于我们的一款数据库版本管理工具。
使用Flyway ,我们需要准备Flyway 将要执行的数据库脚本(Flyway 支持sql 脚本与java 代码,这里认为在Flyway 下执行数据库更新操作的java 代码也是一种数据库脚本),然后通过Flyway 提供的几种不同运行方式来执行这些脚本。(以下配置参数说明基于Flyway 1.7 版本,新的2.0 版本在配置参数上有不少变动,与下面的介绍会有不少出入,以下说明仅供参考)
Flyway 的主要任务是管理数据库的版本更新,在Flyway 中称每次数据库更新为一个migration ,为了更顺口,我们下面称之为数据库脚本。Flyway 支持SQL-based migrations 和Java-based migrations 。
Flyway 支持的数据库脚本有sql 脚本与java 代码,sql 脚本即普通的sql 脚本,包含创建数据库、表,更新库表结构,数据插入、更新、删除等sql 语句,java 代码则是通过一个有效的数据源,使用java 语言来进行数据库的操作,这里针对的读者是对数据库操作有一定熟悉程度的群体,不再详细讲解如何编写数据库脚本。
Flyway 的sql 脚本与java 代码都遵循以下默认规约:
本章主要讲解我们常用的三种Flyway 的执行方式,Flyway 除了提供这三种执行方式外,还提供Ant 任务方式的执行方式,有兴趣的同学可以去官方网站获取相关信息,这里不进行描述。
通过命令行方式运行Flyway ,需要下载flyway-commandline 版本并解压到本地,然后flyway (Windows 下flyway.cmd ,Linux 下flyway.sh )命令执行Flyway 相关操作。
下图是flyway-commandline-1.7 解压后的目录结构:
命令行方式的特点与规约
命令行方式运行的配置及使用方法
配置Maven 插件
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 schema_version
flyway/migrations com.kedacom.flywaydemo.migrations UTF-8 ALL FAIL
上面的插件配置包含了几方面的配置信息:
执行Maven 命令进行Flyway 操作(下面列出几种常用的操作)
定义在应用启动时自动运行Flyway 的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
从上面的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 优先处理数据库更新操作
本篇我们介绍了什么是Flyway ,为什么使用Flyway ,以及如何使用Flyway ,但实际产品/ 项目中的情况可能更复杂,仅靠对Flyway 技术使用上的了解并不能达到我们满意的解决方案,为此我将在下一篇中介绍我们结合项目实际的问题形成的一些基于Flyway 的数据库版本管理解决方案。下一篇的内容主要包括: