Liquibase-数据库版本管理
一、数据库版本管理说明
数据库迁移工具很多,这里我们选择Flyway和Liquibase来说主要是两个原因,
一是它们都是Java生态圈的,其次就是Spring Boot提供了这两者的内建支持,可以很快应用到产品中。
1、liquibase介绍
LiquiBase是一个用于数据库重构和迁移的开源工具,通过日志文件的形式记录数据库的变更,然后执行日志文件中的修改,将数据库更新或回滚到一致的状态。
LiquiBase的主要特点有:
支持几乎所有主流的数据库,如MySQL, PostgreSQL, Oracle, Sql Server, DB2等
支持多开发者的协作维护
日志文件支持多种格式,如XML, YAML, JSON, SQL等
支持多种运行方式,如命令行、Spring集成、Maven插件、Gradle插件等
1.1、changelog文件格式
changelog是LiquiBase用来记录数据库的变更,一般放在CLASSPATH下,然后配置到执行路径中。
changelog支持多种格式,主要有XML/JSON/YAML/SQL,其中XML/JSON/YAML除了具体格式语法不同,节点配置很类似,SQL格式中主要记录SQL语句,以下示例仅给出SQL格式的示例,更多的格式示例请参考文档
2、flyway介绍
flyway相对简单,直接将你需要执行的SQL语句保存为文件,放入应用中执行即可。
Flyway的好处在于简单,而且直接书写SQL并不需要额外的学习。社区版的不支持UNDO操作,需要购买企业版。
3、liquibase与flyway比较
Flyway 自动升级(自动发现更新项):Flyway 会将任意版本的数据库升级到最新版本。
Flyway 可以脱离JVM 环境通过命令行执行,可以通过Ant 脚本执行,通过Maven 脚本执行(这样就可以在集成环境自动执行),并且可以在应用中执行(比如在应用启动时执行)。
Flyway 规约优于配置:Flyway 有一套默认的规约,所以不需要修改任何配置就可以正常使用。
Flyway 既支持SQL 脚本,又支持Java 代码:可以使用SQL 脚本执行数据库更新,也可以使用Java 代码来进行一些高级数据升级操作。
Flyway 高可靠性:在集群环境下进行数据库升级是安全可靠的。
Flyway 支持清除已存在的库表结构:Flyway 可以清除已存在的库表结构,可以从零开始搭建您的库表结构,并管理您的数据库版本升级工作
Flyway 支持失败修复。新的2.0 版本提供了repair 功能,用于解决数据库更新操作失败问题。
Liquibase 自动升级,将任意版本的数据库升级到最新版本。
Liquibase 可以根据数据库的情况为你生成最后的迁移语句,同时因为数据库变动首先是被Liquibase解析,所以也可以简单支持回滚。
Liquibase 支持大部分常见的数据库变动操作,比如建表,删表,变动字段等等
Liquibase 可以在不使用SQL的情况下造成数据库变动,其可读性更高一些,特别是团队并不直接使用SQL而整体相关知识储备不完善的情况下优势更明显。
两款数据库迁移工具其实定位上是差别的。一般小项目整体变动不大的用Flyway,大应用和企业应用用Liquibase更合适。
二、spring boot + liquibase
注:spring boot + liquibase此方式仅仅是简单的实现更新数据库表结构,用于启动项目时更新表结构。
1、gradle配置引入liquibase包:
dependencies {
compile group: 'org.liquibase', name: 'liquibase-core', version: '3.5.3'
testCompile group: 'junit', name: 'junit', version: '4.12'
}
2、修改application.yml或加LiquibaseConfig.java
2.1、application.yml
liquibase:
change-log: classpath:/db/changelog/master.xml
user: root
password: 1qaz2wsx
url: jdbc:mysql://127.0.0.1:3306/test_1.0.1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullNamePatternMatchesAll=true&useSSL=true
drop-first: false
2.2、LiquibaseConfig.java
@Configuration
public class LiquibaseConfig {
@Bean
public SpringLiquibase liquibase(DataSource dataSource) {
SpringLiquibase liquibase = new SpringLiquibase();
liquibase.setDataSource(dataSource);
liquibase.setChangeLog("classpath:/db/changelog/master.xml");
liquibase.setContexts("development,test,production");
liquibase.setShouldRun(true);
return liquibase;
}
}
3、新增changelog.xml
3.1、新增master.xml
在src/main.resouces下新增db/changelog/master.xml。
master.xml内容如下:
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">
3.2、新增V1.1__init.sql(以sql语句方式,也可以用xml、yml、json请查看官网)
在src/main.resouces/db/changelog下新增db/changelog/V1.1__init.sql。
V1.1__init.sql内容如下:
--liquibase formatted sql
--changeset whx:1.1
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for test_user_tab
-- ----------------------------
DROP TABLE IF EXISTS `test_user_tab`;
CREATE TABLE `test_user_tab` (
`userId` int(11) NOT NULL AUTO_INCREMENT,
`userAccount` varchar(16) NOT NULL,
`password` varchar(32) NOT NULL,
`userStatus` tinyint(1) NOT NULL DEFAULT '1',
`addTime` datetime NOT NULL,
PRIMARY KEY (`userId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='测试用户表';
--changeset whx:1.2
ALTER TABLE `test_user_tab`
DROP COLUMN `addTime`;
--rollback ALTER TABLE `test_user_tab` ADD COLUMN `addTime` datetime NOT NULL AFTER `userStatus`;
--changeset whx:1.3
ALTER TABLE `test_user_tab`
ADD COLUMN `addTime` datetime NOT NULL AFTER `userStatus`;
--rollback ALTER TABLE `test_user_tab` DROP COLUMN `addTime`;
--changeset whx:1.4
ALTER TABLE `test_user_tab`
DROP COLUMN `addTime`;
--rollback ALTER TABLE `test_user_tab` ADD COLUMN `addTime` datetime NOT NULL AFTER `userStatus`;
--changeset whx:1.5
ALTER TABLE `test_user_tab`
ADD COLUMN `addTime` datetime NOT NULL AFTER `userStatus`;
--rollback ALTER TABLE `test_user_tab` DROP COLUMN `addTime`;
启动项目可以查看数据库此时新增了三张表:
databasechangelog
databasechangeloglock
test_user_tab
三、gradle + liquibase插件
1、修改build.gradle文件
1.1、多模块项目在根路径修改build.gradle
注:此方式将使所有model都有liquibase插件,在执行命令可能因为文件重复导致失败,不建议此方式。
group = 'test'
version = '0.0.1-SNAPSHOT'
buildscript {
ext {
repositories {
mavenCentral()
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "gradle.plugin.org.liquibase:liquibase-gradle-plugin:2.0.1"
}
}
subprojects {
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: "org.liquibase.gradle"
sourceCompatibility = 1.8
repositories {
mavenCentral()
maven {
url 'http://maven.aliyun.com/nexus/content/groups/public/'
}
}
dependencies {
liquibaseRuntime 'org.liquibase:liquibase-core:3.5.3'
liquibaseRuntime 'org.liquibase:liquibase-groovy-dsl:2.0.1'
liquibaseRuntime 'mysql:mysql-connector-java:5.1.34'
}
liquibase {
activities {
main {
changeLogFile "${this.rootDir}/user/src/main/resources/db/changelog/V1.1__init.sql"
url "jdbc:mysql://127.0.0.1:3306/test_1.0.1?useUnicode=true&characterEncoding=UTF-8"
username "root"
password "1qaz2wsx"
}
runList = 'main'
}
}
}
1.2、子模块修改build.gradle
例如:core model的core.gradle文件,推荐此方式。
group 'projectManage'
version '1.0-SNAPSHOT'
apply plugin: 'java'
sourceCompatibility = 1.8
buildscript {
repositories {
mavenCentral()
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
classpath "org.liquibase:liquibase-gradle-plugin:2.0.1"
}
}
apply plugin: 'org.liquibase.gradle'
dependencies {
liquibaseRuntime 'org.liquibase:liquibase-core:3.5.3'
liquibaseRuntime 'org.liquibase:liquibase-groovy-dsl:2.0.1'
liquibaseRuntime 'mysql:mysql-connector-java:5.1.34'
compile project(":common")
testCompile group: 'junit', name: 'junit', version: '4.12'
liquibase {
activities {
main {
changeLogFile "${this.rootDir}/core/src/main/resources/db/changelog/V1.1__init.sql"
//changeLogFile "${this.rootDir}/core/src/main/resources/db/changelog/changelog.mysql.sql"
url "jdbc:mysql://127.0.0.1:3306/yb_oa_xmgl_1.0.1?useUnicode=true&characterEncoding=UTF-8"
username "root"
password "1qaz2wsx"
}
runList = 'main'
}
}
}
说明:
0、注意mysql驱动版本
liquibaseRuntime 'mysql:mysql-connector-java:5.1.34'
1、注意${this.rootDir}
changeLogFile "${this.rootDir}/user/src/main/resources/db/changelog/V1.1__init.sql"
如果不加${this.rootDir}可能会报Gradle Liquibase change log file could not be found
另一种写法:
liquibase {
activities {
main {
changeLogFile 'src/main/resources/db/dbchangelog-master.xml'
url 'jdbc:mysql://localhost:3306/test'
username 'XXX'
password 'XXX'
classpath "$rootDir"
}
}
runList = 'main'
}
2、命令示例
0、插件命令 gradle command -PliquibaseCommandValue= # -PliquibaseCommandValue= 为非填。
1、gradle update -PrunList=main #执行命令,将按V1.1__init.sql中的sql语句更新数据库。
2、gradle generateChangeLog #数据库sql文件的标准输出,执行于已存在表结构的数据库将会得到表数据结构文件。
注:changeLogFile 'src/main/resources/db/changelog.mysql.sql' ,changelog.xxx.sql允许不存在,执行时将自动创建。xxx代表的是数据库。
例如:changelog.h2.sql,必须是此格式,否则将报错。
3、gradle rollbackCount -PliquibaseCommandValue=1 # rollbackCount 回滚最后的更改集
4、gradle dbDoc # 将在/项目路径/.idea/dataSources下生成/xxx.xml文件,该文件是对数据库表数据结构的描述。
3、命令详解
liquibase官网:https://www.liquibase.org/documentation/command_line.html
注:插件命令 gradle command -PliquibaseCommandValue=
以下操作均已/core/src/main/resources/db/changelog/V1.1__init.sql中的V1.1__init.sql为例子。
3.1、数据库更新命令
命令描述
update
更新数据库到当前版本。
updateCount
更新数据库到指定的value版本,即第几个changeset。
例如:gradle updateCount -PliquibaseCommandValue=1
将只会执行V1.1__init.sql中--changeset whx:1.1,后续的changeset将不会执行。
若继续执行value=5,则--changeset whx:1.1~1.5都将执行,且之前的操作并不会冲突。
updateSQL
预执行sql(执行全部changeset),并不会更新数据库,仅在控制台输出。
updateCountSQL
预执行sql到指定的value版本。
3.2、数据库回滚命令
Liquibase有三种管理回滚的模式:
3.2.1、直接执行回滚
可以直接针对目标数据库执行回滚命令。如果无法回滚任何更改,您将收到通知,并且不会回滚任何更改。
3.2.2、生成回滚脚本
可以生成回滚数据库所需的SQL,而不是实际更新数据库。如果要预览在实际运行之前将执行的回滚命令,这将非常有用。
3.2.3、生成“未来回滚”脚本
此模式旨在允许在生成迁移脚本的同时生成回滚脚本。它允许获取更新的应用程序并生成SQL以将数据库更新为新版本以及SQL,以便在需要时将该新版本恢复到当前版本。当DBA想要控制进入数据库的SQL时,以及需要内部和/或“SOX兼容”进程的回滚文档的应用程序时,此功能非常有用。无需在此模式下指定回滚日期,标记或计数。
3.2.4 回滚命令
命令描述
rollback
要回滚到那个tag。例如:gradle rollback -PliquibaseCommandValue=tag20190107。
需要注意的是在V1.1__init.sql中,必须有--rollback。
例如:
--changeset whx:1.5
ALTER TABLE `test_user_tab`
ADD COLUMN `addTime` datetime NOT NULL AFTER `userStatus`;
--rollback ALTER TABLE `test_user_tab` DROP COLUMN `addTime`;
初始版本的sql数据表结构可以没有--rollback,但是后续的--changeset建议都加上--rollback。
rollbackToDate
设置回滚的日期。日期格式要符合插件执行theDateFormat.getDateInstance()操作设置的日期格式。
rollbackCount
value指定往前回滚几个版本,例如:gradle rollbackCount -PliquibaseCommandValue=1
将会回滚--changeset whx:1.5的操作。
rollbackSQL
根据tag,预执行rollback。并不会更新数据库,仅在控制台输出。
rollbackToDateSQL
根据date/time,预执行rollback。
rollbackCountSQL
根据value,预执行rollback。
futureRollbackSQL
SQL以在应用更改日志中的更改后将数据库回滚到当前状态。
updateTestingRollback
更新数据库,然后在更新之前回滚更改。
generateChangeLog
数据库sql文件的标准输出,执行于已存在表结构的数据库将会得到表数据结构文件。
注:changeLogFile 'src/main/resources/db/changelog.mysql.sql',
changelog.xxx.sql允许不存在,执行时将自动创建。xxx代表的是那种数据库。
例如:changelog.h2.sql,必须是此格式,否则将报错。
3.3、差异命令
diff命令用于比较数据库之间的异同。
注:它目前不检查:非外键约束(检查等)、存储过程、数据类型长度。
详见 3.11、liquibase.activities详解。
命令描述
diff [diff parameters]
将差异描述写入标准输出。
例如:gradle diff -PrunList=diffMain。
diffChangeLog [diff parameters]
写入更改日志XML以将基础数据库更新到目标数据库以标准输出,默认控制台输出。
例如:gradle diffChangeLog -PrunList=diffMain。
配置changeLogFile 则按规则输出。
3.4、文档命令
命令描述
dbDoc
默认将在/项目路径/.idea/dataSources下生成/xxx.xml文件,该文件是对数据库表数据结构的描述。
outputDirectory指定输出路径,不同后缀文件名将生产不同格式的Doc。
例如 gradle dbDoc -PliquibaseCommandValue=
D:\work_idea\yboa\pro_manage_dev\user\src\main\resources\db\changelog\123.sql
将生成html文件。
3.5、维护命令
命令描述
tag
"标记"当前数据库状态以供将来回滚。
例如:gradle tag -PliquibaseCommandValue=tag20190107
tagExists
检查给定标记是否已存在。
status
validate
检查更改日志中的错误。
changelogSync
将所有更改标记为在数据库中执行。
changelogSyncSQL
SQL以将在数据库中执行的所有更改标记为STDOUT。
markNextChangeSetRan
将下一个更改集标记为在数据库中执行。
listLocks
列出当前锁定数据库更改日志的人员。
releaseLocks
释放数据库更改日志上的所有锁定。
dropAll
删除用户拥有的所有数据库对象。请注意,不删除函数,过程和包(1.8.1中的限制)。
clearCheckSums
从数据库中删除当前的校验和。在下次运行时,将重新计算校验和。
3.6、必需参数
详见:build.gradle文件中的配置
命令描述
--changeLogFile=
要使用的changelog文件。
--username=
数据库用户名
--password=
数据库密码。
--url=
数据库JDBC URL。
--driver=
数据库驱动程序类名。
3.7、可选参数
命令描述
--classpath=
包含迁移文件和JDBC驱动程序的类路径。
--contexts=
ChangeSet上下文要执行。
--defaultSchemaName=
指定用于托管数据库对象和Liquibase控制表的默认架构。
--databaseClass=
指定要使用的自定义数据库实现
--defaultsFile=
包含默认选项值的文件。(默认值:./ liquibase.properties)
--includeSystemClasspath=
在Liquibase类路径中包含系统类路径。(默认值:true)
--promptForNonLocalDatabase=
提示非本地主机数据库。(默认值:false)
--currentDateTimeFunction=
覆盖SQL中使用的当前日期时间函数。适用于不受支持的数据库。
--logLevel=
执行日志级别((debug, info, warning, severe, off)。
--help
输出命令行参数帮助。
--exportDataDir
将保留insert语句csv文件的目录(generateChangeLog命令所需)。
--propertyProviderClass=
要使用的自定义Properties实现
3.8、必需的Diff参数
命令描述
--referenceUsername=
基础数据库用户名
--referencePassword=
基础数据库密码。
--referenceUrl=
基础数据库URL。
3.9、可选的Diff参数
命令描述
--referenceDriver=
基础数据库驱动程序类名。
3.10、更改日志属性
命令描述
-D=
传递名称/值对以替换更改日志中的$ {}块。
3.11、liquibase.activities详解
liquibase {
activities {
main {
changeLogFile "${this.rootDir}/core/src/main/resources/db/changelog/V1.1__init.sql"
//changeLogFile "${this.rootDir}/core/src/main/resources/db/changelog/changelog.mysql.sql"
url "jdbc:mysql://127.0.0.1:3306/yb_oa_xmgl_1.0.1?useUnicode=true&characterEncoding=UTF-8"
username "root"
password "1qaz2wsx"
//driver "" // 该参数可非必填,url将自动匹配驱动。
//exportDataDir // 将保留insert语句csv文件的目录(generateChangeLog命令所需)。
}
security {
changeLogFile 'src/main/db/security.groovy'
url project.ext.securityUrl
username project.ext.securityUsername
password project.ext.securityPassword
}
/**比较数据库之间的差异**/
diffMain {
//若不配置changeLogFile 则将在控制台进行xml输出。diff.mysql.sql会自动创建,必须以 *.databaseType.sql才会生成sql文件
changeLogFile "${this.rootDir}/core/src/main/resources/db/changelog/diff.mysql.sql"
url "jdbc:mysql://127.0.0.1:3306/yb_oa_xmgl_1.0.1?useUnicode=true&characterEncoding=UTF-8"
username "root"
password '1qaz2wsx'
referenceUrl "jdbc:mysql://127.0.0.1:3306/yb_oa_xmgl_1.0.2?useUnicode=true&characterEncoding=UTF-8"
referenceUsername "root"
referencePassword '1qaz2wsx'
}
runList = 'main' # 不同环境可执行不同的main方法, 例如:runList = 'diffMain'
}
}
4、插件升级
升级Liquibase Gradle插件本身的版本
大多数时候,Liquibase的新版本与旧版本的版本相同,但有时新版本与现有的更改集存在兼容性问题,就像Liquibase 3发布时一样。
发生这种情况时,建议执行以下升级程序:
1、确保所有Liquibase的管理数据库是最新通过运行 gradle update它们升级到Liquibase插件的新版本之前。
2、创建一个新的丢弃数据库来测试Liquibase更改集。gradle update使用最新版本的Liquibase插件在新数据库上运行 。
这很重要,因为Groovy DSL中的项目已弃用,并且因为不同Liquibase版本生成SQL的方式存在一些细微差别。
例如,使用defaultValue: "0"Liquibase 2中的工作正常,在MySql中向布尔列添加默认值,但在Liquibase 3中,
它生成的SQL不适用于MySql - defaultValueNumeric: 0需要使用它。
3、一旦确定所有更改集都使用最新的Liquibase插件,请清除所有由Liquibase 2旧版本计算的校验和,方法是gradle clearChecksums对所有数据库运行。
4、最后,gradle changeLogSync在所有数据库上运行以计算新的校验和。