1. 概述
Flyway是独立于数据库的应用、管理并跟踪数据库变更的数据库版本管理工具。
自动升级(自动发现更新项):Flyway 会将任意版本的数据库升级到最新版本。Flyway 可以脱离JVM 环境通过命令行执行,可以通过Ant 脚本执行,通过Maven 脚本执行(这样就可以在集成环境自动执行),并且可以在应用中执行(比如在应用启动时执行)。
规约优于配置:Flyway 有一套默认的规约,所以不需要修改任何配置就可以正常使用。
既支持SQL 脚本,又支持Java 代码:可以使用SQL 脚本执行数据库更新,也可以使用Java 代码来进行一些高级数据升级操作。
高可靠性:在集群环境下进行数据库升级是安全可靠的。
支持清除已存在的库表结构:Flyway 可以清除已存在的库表结构,可以从零开始搭建您的库表结构,并管理您的数据库版本升级工作。
支持失败修复。新的2.0 版本提供了repair 功能,用于解决数据库更新操作失败问题。
2. flyway基础
2.1概述
- flyway通过在数据库中维护一张版本记录表(默认名称为flyway_schema_history),对数据库脚本操作进行版本管理,使得数据库的升级更新可以自动化;
- flyway执行的脚本支持两种形式:一是使用SQL编写的.sql脚本,二是把SQL操作写到java代码里,通过执行java代码来执行SQL;
- 每个.sql或.class文件都是一个版本,文件名称需要遵循flyway约定的格式;这个名称中包含了一个数字的版本信息,flyway通过这个版本来进行管理;
2.2 flyway组成
- flyway包由三部分组成
- commandline: 对输入的命令进行处理,确定配置文件路径,查找依赖包等;
- core: 实现flyway核心的操作(migrate/info/clean等)和版本管理
- maven-plugin和gradle-plugin: 用于帮助打可运行的jar包;
- flyway由java语言编写,可以直接使用core来进行开发,而不需要使用commandline
- 比如:自己建个springboot启动工程(web或application),在里面实例化Flyway对象,然后执行对应的操作
- 比如:自己写个main方法,在里面实例化Flyway对象,然后执行对应的操作
2.3 flyway操作
命令 | 说明 |
---|---|
migrate | 执行最新版本的SQL脚本和java代码,把结果反映到数据库上 |
clean | 清除数据库中的所有表与数据,只剩下一个空的数据库 |
info | 列出用于维护版本信息的表内容,表名默认为flyway_schema_history |
validate | 用实际的版本与数据库中已有的版本进行对比,检测意外修改(文件有checksum) |
baseline | 设定基线版本,对于已存在数据的数据,必须设置基线版本,后面从该版本之后的版本进行操作 |
repair | 把版本表中失败的记录状态变成pending,然后可以重新操作 |
undo | 回滚对应版本,只有商用版本才支持 |
2.4 版本
- Java文件
- SQL文件
- SQL文件与Java文件的不同之处仅在于后缀不同,一个是.sql(可配置),另外一个是.class(不可配置),其它是相同的;
- 文件名(不含后缀)的格式
- Prefix: 前缀,可以通过配置文件来设置
- V: 代表正常版本管理;
- U: 表示回滚,该操作比较危险,需要做详尽测试才能用,实践中最好不用;
- R: 表示可以重复执行的版本,比如建视图、存储过程、函数等;
- Version: 版本号,必须由数字(可加下划线或点)组成,重复执行的脚本不需要版本;
- Separator: 分隔版本与description的分隔符,为两个下划线,可通过配置文件来修改,默认为两个下划线;
- Description: 文件内容描述,建议用下划线分隔单词;
- Suffix: 只针对于sql文件(java文件不适用),可通过配置文件来设置,默认为.sql;
- Prefix: 前缀,可以通过配置文件来设置
- Version
- 一般情况下使用1~N的数字即可;
- 考虑到实际开发中可能有多个分支进行操作,为了使得分支合并不冲突,可使用时间戳来做版本(中间可用下划线或点来分隔),时间戳越精确、冲突可能性越小;
2.5 路径
- INSTALLDIR: 安装路径,即flyway解压所在的路径,其用于:
- 加载${INSTALLDIR}/drivers下的各种数据库driver对应的jar包;
- 加载${INSTALLDIR}/conf下的全局配置文件;
- workingDirectory: 工作目录,可通过指定命令参数“-workingDirectory=xxxx”(不含双引号,xxx换成具体的绝对路径),如果不指定则等同INSTALLDIR;
- 加载${workingDirectory}/jars里的jar包,通过java编写的代码需要打成jar放到该目录下才能通过命令行的方式访问到;
- 加载${workingDirectory}/sql里的SQL脚本文件,这个目录会被配置文件flyway.conf里的flyway.locations的值替换掉(注意是替换掉,不是并集,这个地方不好理解,原理是这两个目录都是赋值给location变量,配置文件里有设置时会覆盖掉);
- 在flyway.conf里的flyway.locations值,如果以filesystem:开头、且是相对路径的,都会基于此路径;
- 注意:该部分在flyway官网文档中无说明(需要查看源码才能知晓),也不好理解,容易出错,需要好好理解(有比较多的地方并不遵循后面覆盖前面的逻辑,所以不好理解)。这些路径是在commandline中实现的,也就是说只有使用commandline才用到
2.6 配置
2.6.1 配置加载
配置主要来源于flyway.conf配置文件,按以下顺序和路径进行加载配置:
- 先加载${INSTALLDIR}/conf/flyway.conf(flyway.conf文件名不可更改);
- 加载System.property中配置user.home对应的路径下的flyway.conf文件(flyway.conf文件名不可更改),加载的配置若有相同则会覆盖之前加载的;
- System properties可以通过下面方式增加
- java启动时使用-D加的参数,如java -Dxxx=xxxx;
- 在java代码中使用System.setProperty(“xxx”, “xxxx”)添加的参数;
- System properties可以通过下面方式增加
- 加载当前目录下的flyway.conf文件(flyway.conf文件名不可更改),加载的配置若有相同则会覆盖之前加载的;
- 在命令行中,terminal或cmd进入的路径为当前路径
- 在java工程中运行则是工程根目录
- 该操作有点隐蔽,注意理解;
- 若在环境变量中配置了flyway.configFiles,会加载这些文件,加载的配置若有相同则会覆盖之前加载的;
- 该配置值可以有多个文件,用逗号分隔,每个文件可以有相对路径(不能使用绝对路径),文件名也不一定是flyway.conf;
- 这些配置文件会基于{workingDirectory}目录下;
- 若命令行参数中带-configFiles参数指定了配置文件,则会加载这些文件,加载的配置若有相同则会覆盖之前加载的;
- 若环境变量中配置了flyway.configFiles,则此配置参数无效;
- 该配置值可以有多个文件,用逗号分隔,每个文件可以有相对路径(不能使用绝对路径),文件名也不一定是flyway.conf;
- 这些配置文件会基于{workingDirectory}目录下;
- System.in输入的配置(貌似用于测试,待研究);
- 用逗号分隔flyway.locations的值,如果里面的路径不是绝对路径,则用${workingDirectory}补齐;
- 命令行还可以指定-flyway.xxxx=xxx形式的配置,这些配置会覆盖上面已载的配置项
- 注意:此处配置项,不是加载配置文件;
注意:
- 该部分在flyway官网文档中无说明(需要查看源码才能知晓),也不好理解,因为配置文件名大多是固定不可变的,而可以改变的手段有点特殊。
- 要改变flyway.conf配置文件名称,只有两种方式;
- 该部分主要属于commandline范畴,直接使用core则可以自行构造配置项或自行指定配置文件并加载;
- 配置里的路径可以使用变量(原理待研究);
- 在命令行中指定-workingDirectory=xxxx时,若也指定-configFiles=flyway.conf,则会比较明显地知道配置文件是在workingDirectory目录下,但如果不指定configFiles则需要flyway.conf放到命令行进入的目录中,会有点隐蔽;
- 配置文件里参数读取后,{}整体被替换为空(来源于命令行的参数没有此处理过程,故不能使用${}格式的变量);
2.6.2 常用配置
# 配置sql和java code查找路径:
# sql文件路径必须以filesystem:(含冒号)开头,可以使用变量${workdir};
# java code路径需要以classpath:(含冒号)开头,java代码需要打成jar包,并放到工作目录里的jars目录下,jar里的目录与该配置中classpath:后配置的路径一致;
# 开发环境不要修改此值,它会自动找工程目录下的db/sql中的sql脚本,以及代码目录com/everhomes/dbmigrate中的java代码(两种都会递归找子目录)
flyway.locations=filesystem:${workdir}/sql,classpath:com/everhomes/dbmigrate
# 配置数据库连接,注意在url中不要配置用户名和密码
flyway.url=jdbc:mysql://127.0.0.1:13306/blogs?characterEncoding=UTF-8&serverTimezone=UTC
flyway.user=root
flyway.password=123456
# 当需要低版本的脚本时,需要置成true,否则只能执行高版本的脚本
flyway.outOfOrder=true
# 当数据库不是空数据库时,需要配置为true,空数据库则可以不配置
# flyway.baselineOnMigrate=true
# 配置了该版本后,migrate会从比该版本大的版本进行migrate,不配置时默认值为1
# flyway.baselineVersion=1
# 有数据的数据库请保留此值为true,禁用clean命令,执行clean命令会清空数据库
flyway.cleanDisabled=true
# 记录migrate历史记录的表名,默认为 flyway_schema_history
flyway.table=schema_version_history
# migrate前后需要做某些操作时可使用此配置项,值为类名(带包路径),多个类名之间使用逗号分隔
# 每个类需要实现org.flywaydb.core.api.callback.Callback接口
# flyway.callbacks=com.everhomes.dbmigrate.core.MigrateCallBack
2.7 版本管理
支持SQL-base和Java-base两种管理方式,两种方式产生的版本文件是共同管理的,即在命令行或Java代码工程里都有可以同时执行.sql和.class提供的版本管理,执行顺序按版本号顺序;
2.7.1 SQL-base migrate
基于SQL的版本管理:
- 文件名称需要符合版本格式
- 文件可以被找到
- SQL文件是通过flyway.locations配置项中指定以filesystem:开头的路径,可以有多个、用逗号分隔;
- flyway.locations可以通过配置文件或命令行参数指定(参考上面“配置加载”章节说明);
- 注意:flyway.locations值虽然可以通过多个途径配置,但只能配置一个值(值中可逗号分隔),注意加载的顺序和覆盖情况;
- 确定上面两点后,即可执行flyway migrate/info/clean/repair 来进行版本管理;
- 命令参数中可以按规范指定参数来帮助找到flyway.locations
2.7.2 Java-base migrate
基于Java的版本管理:
- jar形式
- 把开发的java代码打包jar包,放到${workingDirectory}/jars中
- java代码须继承org.flywaydb.core.api.migration.BaseJavaMigration(推荐)或实现org.flywaydb.core.api.migration.JavaMigration接口,使用此方式仅依赖flyway和jdk提供的jar即可,配置环境简单;
- java代码尽量使用flyway和jdk提供的类和自定义的类来进行操作,否则要解决包依赖问题;
- java代码路径可被找到(在jar包里的路径)
- java代码如果是放到db/migration路径下(可有子目录),则不用配置其它路径即可访问,因为flyway里默认给location加了个目录: classpath:db/migration
- 如果不是放到db/migration路径下,则需要在flyway.locations里配置以classpath:开头的路径,该路径要与jar包里的路径一致;
- 把开发的java代码打包jar包,放到${workingDirectory}/jars中
- java工程形式(在IDE里运行java工程的形式)
- 工程启动运行有两种方式
- 引flyway的commandline包
- 可指定flyway的安装目录,或者自行依赖数据库driver包和flyway的core包;
- 指定workingDirectory目录;
- 指定flyway.conf的目录,或按规则放到commandline可找到的目录;
- 运行后执行commandline提供的org.flywaydb.commandline.Main.main(args)
- 优点:可利用commandline已经实现的命令操作,按命令的方式操作;
- 缺点:需要遵循commandline的方式;
- 实例化flyway并运行
- 按springboot的方式启动或自行写main方法启动
- 把flyway的core引入到工程中
- 实例化flyway,并指定数据库连接,然后调用migrate()/clean()等方法
- 优点:灵活;
- 缺点:需要对flyway各命令所依赖的配置项有比较多了解,才好操作;
- 引flyway的commandline包
- 该方式不受限于db/migration目录,可随意定目录,但为了部署时可以打包jar包,推荐遵循jar包所要求的目录方式;
- 工程启动运行有两种方式
- 注意:
- 该部署在官方文档说明不详细,比较模糊,特别是jar的方式就没有说明,部署时也只能使用jar方式;
- 官方文档主要说明了“实例化flyway”方式,但其背后依赖的配置项和规则也没有说明;
2.7.3 callback
- 版本管理操作有生命周期管理:整个操作开始前->单个版本操作开始前->单个statement操作开始前-> 具体操作 -> 单个statement操作后(报错后)-> 单个版本操作后(错误后)-> 整个操作后(错误后)
- 整个操作可能包含多个版本,每个版本里可能有多个sql statement
- 操作是指migrate、clean、repair等,参考上面操作说明
- 这些生命周期节点可以设置callback来进行一些额外的处理
- 在java代码中,需要实现org.flywaydb.core.api.callback.Callback接口,类路径无规定
- 接口要实现supports()方法,在里面指定哪些操作归该Callback进行处理
- 实现handle()方法,具体处理业务
- canHandleInTransaction()在有事务时才涉及;
- 在flyway.callbacks配置中指定callback的类路径(带包名,多个类之间使用逗号分隔),flyway.callbacks可以在flyway.conf中指定或者在命令行参数中指定(参考上面配置加载章节说明);
- callback的类包需要包含在jar中,才能在命令行里操作;
- 在java代码中,需要实现org.flywaydb.core.api.callback.Callback接口,类路径无规定
2.7.4 不支持MySQL5.6
- 在org.flywaydb.core.internal.database.mysql.MySQLDatabase.ensureSupported()中有对MySQL数据库的版本进行判断,小于5.7的版本不支持(MARIADB是MySQL的一个分支,都在MySQLDatabase中实现)。
- 除此之外,没有其它地方控制这个版本。抛出的错误说enterprise版本支持,但除非换代码,否则感觉也不支持。
- 要支持5.6,可以修改此类编译与.class,然后替换掉core包里的该类。
public final void ensureSupported() {
ensureDatabaseIsRecentEnough("5.1");
if (databaseType == DatabaseType.MARIADB) {
ensureDatabaseNotOlderThanOtherwiseRecommendUpgradeToFlywayEdition("10.1", org.flywaydb.core.internal.license.Edition.ENTERPRISE);
ensureDatabaseNotOlderThanOtherwiseRecommendUpgradeToFlywayEdition("10.2", org.flywaydb.core.internal.license.Edition.PRO);
recommendFlywayUpgradeIfNecessary("10.4");
} else {
ensureDatabaseNotOlderThanOtherwiseRecommendUpgradeToFlywayEdition("5.7", org.flywaydb.core.internal.license.Edition.ENTERPRISE);
if (JdbcUtils.getDriverName(jdbcMetaData).contains("MariaDB")) {
LOG.warn("You are connected to a MySQL " + getVersion() + " database using the MariaDB driver." +
" This is known to cause issues." +
" An upgrade to Oracle's MySQL JDBC driver is highly recommended.");
}
recommendFlywayUpgradeIfNecessary("8.0");
}
}
protected final void ensureDatabaseNotOlderThanOtherwiseRecommendUpgradeToFlywayEdition(String oldestSupportedVersionInThisEdition, Edition editionWhereStillSupported) {
if (!getVersion().isAtLeast(oldestSupportedVersionInThisEdition)) {
throw new FlywayEditionUpgradeRequiredException(
editionWhereStillSupported,
databaseType,
computeVersionDisplayName(getVersion()));
}
}
2.7.5 版本比较
- flyway把版本都封装在org.flywaydb.core.api.MigrationVersion类里
- 比较的原理
- 先把版本中的下划线(_)替换成点(.),不支持其它非数字字符,有的话会报错;
- 把版本以点进行分隔成多个数字(点分隔的每段一个数字);
- 把每一段数字转成Bigdecimal,即版本的每段数字都可以很大,即使精确到毫秒的时间戳作为版本也支持;
- 两个版本之间进行比较
- 取两个版本中段数量大的段数作为循环次数;
- 循环比较,从左到右,每一段相比(Bidecimal相比),某段大的则版本大,若段数不够则用0代替;
- 注意
- 每段都是数字值相比,不是按字符串相比
- 段数的多或少并不能说明版本的大小,比如并不是段数多的版本大;
- 段是从左到右比的,哪段先大就版本大;
2.8 日志
- flyway本身提供了日志的方式,但实现也比较混乱
- 比如可以通过命令行参数指定-outputFile=xxx或-logFile=xxx来输出日志文件,通过指定-X或-q来分别控制日志的级别为DEBUG或WARN,但这些参数生效的前提是在环境中找不到slf4j等常用的日志方式才会生效
- 这些日志的格式无法控制,打印出来的日志时间没有日期
- 使用logback
- 依赖slf4j相关的三个包:logback-classic-1.2.3.jar、logback-core-1.2.3.jar、slf4j-api-1.7.25.jar
- java代码工程环境直接依赖即可
- 命令行环境则需要把这些包放到${INSTALLDIR}/lib,可能也可以用其它方式依赖(待研究);
- logback.xml文件路径
- 在java代码工程环境中,如果是使用springboot环境,则直接放到classpath中即可找到(springboot有自己找logback的方式),否则依赖slf4j的查找方式(待研究);
- 在命令行环境中,可在java命令启动的时候加入-Dlogback.configurationFile=xxx/logback.xml 参数来指定logback.xml文件(不确定是否有更优雅的方式,待研究)
- 依赖slf4j相关的三个包:logback-classic-1.2.3.jar、logback-core-1.2.3.jar、slf4j-api-1.7.25.jar