數據庫遷移工具不少,這裏咱們選擇Flyway和Liquibase來講主要是兩個緣由, 一是它們都是Java生態圈的,其次就是Spring Boot提供了這二者的內建支持,能夠很快應用到產品中。
LiquiBase是一個用於數據庫重構和遷移的開源工具,經過日誌文件的形式記錄數據庫的變動,而後執行日誌文件中的修改,將數據庫更新或回滾到一致的狀態。 LiquiBase的主要特色有: 支持幾乎全部主流的數據庫,如MySQL, PostgreSQL, Oracle, Sql Server, DB2等 支持多開發者的協做維護 日誌文件支持多種格式,如XML, YAML, JSON, SQL等 支持多種運行方式,如命令行、Spring集成、Maven插件、Gradle插件等
changelog是LiquiBase用來記錄數據庫的變動,通常放在CLASSPATH下,而後配置到執行路徑中。 changelog支持多種格式,主要有XML/JSON/YAML/SQL,其中XML/JSON/YAML除了具體格式語法不一樣,節點配置很相似,SQL格式中主要記錄SQL語句,如下示例僅給出SQL格式的示例,更多的格式示例請參考文檔
flyway相對簡單,直接將你須要執行的SQL語句保存爲文件,放入應用中執行便可。 Flyway的好處在於簡單,並且直接書寫SQL並不須要額外的學習。社區版的不支持UNDO操做,須要購買企業版。
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此方式僅僅是簡單的實現更新數據庫表結構,用於啓動項目時更新表結構。php
dependencies {
compile group: 'org.liquibase', name: 'liquibase-core', version: '3.5.3' testCompile group: 'junit', name: 'junit', version: '4.12' }
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
@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; } }
在src/main.resouces下新增db/changelog/master.xml。
master.xml內容以下:html
<?xml version="1.0" encoding="utf-8"?> <databaseChangeLog 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"> <include file="classpath:/db/changelog/V1.1__init.sql" relativeToChangelogFile="false"/> </databaseChangeLog>
在src/main.resouces/db/changelog下新增db/changelog/V1.1__init.sql。
V1.1__init.sql內容以下:java
--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`;
啓動項目能夠查看數據庫此時新增了三張表:mysql
databasechangelog databasechangeloglock test_user_tab
plugin地址:https://github.com/liquibase/liquibase-gradle-plugingit
注:此方式將使全部model都有liquibase插件,在執行命令可能由於文件重複致使失敗,不建議此方式。github
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' } } }
例如:core model的core.gradle文件,推薦此方式。spring
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'
一、注意${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
另外一種寫法:sql
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' }
0、插件命令 gradle command -PliquibaseCommandValue=<value> # -PliquibaseCommandValue=<value> 爲非填。 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 回滾最後的<value>更改集 4、gradle dbDoc # 將在/項目路徑/.idea/dataSources下生成/xxx.xml文件,該文件是對數據庫表數據結構的描述。
liquibase官網:https://www.liquibase.org/documentation/command_line.html
注:插件命令 gradle command -PliquibaseCommandValue=<value>
如下操做均已/core/src/main/resources/db/changelog/V1.1__init.sql中的V1.1__init.sql爲例子。數據庫
命令 | 描述 |
---|---|
update | 更新數據庫到當前版本。 |
updateCount <value> | 更新數據庫到指定的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 <value> | 預執行sql到指定的value版本。 |
Liquibase有三種管理回滾的模式:json
能夠直接針對目標數據庫執行回滾命令。若是沒法回滾任何更改,您將收到通知,而且不會回滾任何更改。
能夠生成回滾數據庫所需的SQL,而不是實際更新數據庫。若是要預覽在實際運行以前將執行的回滾命令,這將很是有用。
此模式旨在容許在生成遷移腳本的同時生成回滾腳本。它容許獲取更新的應用程序並生成SQL以將數據庫更新爲新版本以及SQL,以便在須要時將該新版本恢復到當前版本。當DBA想要控制進入數據庫的SQL時,以及須要內部和/或「SOX兼容」進程的回滾文檔的應用程序時,此功能很是有用。無需在此模式下指定回滾日期,標記或計數。
命令 | 描述 |
---|---|
rollback <tag> | 要回滾到那個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 <date/time> | 設置回滾的日期。日期格式要符合插件執行theDateFormat.getDateInstance()操做設置的日期格式。 |
rollbackCount <value> | value指定往前回滾幾個版本,例如:gradle rollbackCount -PliquibaseCommandValue=1 將會回滾--changeset whx:1.5的操做。 |
rollbackSQL <tag> | 根據tag,預執行rollback。並不會更新數據庫,僅在控制檯輸出。 |
rollbackToDateSQL <date/time> | 根據date/time,預執行rollback。 |
rollbackCountSQL <value> | 根據value,預執行rollback。 |
futureRollbackSQL | SQL以在應用更改日誌中的更改後將數據庫回滾到當前狀態。 |
updateTestingRollback | 更新數據庫,而後在更新以前回滾更改。 |
generateChangeLog | 數據庫sql文件的標準輸出,執行於已存在表結構的數據庫將會獲得表數據結構文件。 注:changeLogFile 'src/main/resources/db/changelog.mysql.sql', changelog.xxx.sql容許不存在,執行時將自動建立。xxx表明的是那種數據庫。 例如:changelog.h2.sql,必須是此格式,不然將報錯。 |
diff命令用於比較數據庫之間的異同。
注:它目前不檢查:非外鍵約束(檢查等)、存儲過程、數據類型長度。
詳見 3.十一、liquibase.activities詳解。
命令 | 描述 |
---|---|
diff [diff parameters] | 將差別描述寫入標準輸出。 例如:gradle diff -PrunList=diffMain。 |
diffChangeLog [diff parameters] | 寫入更改日誌XML以將基礎數據庫更新到目標數據庫以標準輸出,默認控制檯輸出。 例如:gradle diffChangeLog -PrunList=diffMain。 配置changeLogFile 則按規則輸出。 |
命令 | 描述 |
---|---|
dbDoc <outputDirectory> | 默認將在/項目路徑/.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文件。 |
命令 | 描述 |
---|---|
tag <tag> | "標記"當前數據庫狀態以供未來回滾。 例如:gradle tag -PliquibaseCommandValue=tag20190107 |
tagExists <tag> | 檢查給定標記是否已存在。 |
status | |
validate | 檢查更改日誌中的錯誤。 |
changelogSync | 將全部更改標記爲在數據庫中執行。 |
changelogSyncSQL | SQL以將在數據庫中執行的全部更改標記爲STDOUT。 |
markNextChangeSetRan | 將下一個更改集標記爲在數據庫中執行。 |
listLocks | 列出當前鎖定數據庫更改日誌的人員。 |
releaseLocks | 釋放數據庫更改日誌上的全部鎖定。 |
dropAll | 刪除用戶擁有的全部數據庫對象。請注意,不刪除函數,過程和包(1.8.1中的限制)。 |
clearCheckSums | 從數據庫中刪除當前的校驗和。在下次運行時,將從新計算校驗和。 |
詳見:build.gradle文件中的配置
命令 | 描述 |
---|---|
--changeLogFile=<path and filename> | 要使用的changelog文件。 |
--username=<value> | 數據庫用戶名 |
--password=<value> | 數據庫密碼。 |
--url=<value> | 數據庫JDBC URL。 |
--driver=<jdbc.driver.ClassName> | 數據庫驅動程序類名。 |
命令 | 描述 |
---|---|
--classpath=<value> | 包含遷移文件和JDBC驅動程序的類路徑。 |
--contexts=<value> | ChangeSet上下文要執行。 |
--defaultSchemaName=<schema> | 指定用於託管數據庫對象和Liquibase控制表的默認架構。 |
--databaseClass=<custom.DatabaseImpl> | 指定要使用的自定義數據庫實現 |
--defaultsFile=</path/to/file> | 包含默認選項值的文件。(默認值:./ liquibase.properties) |
--includeSystemClasspath=<true or false> | 在Liquibase類路徑中包含系統類路徑。(默認值:true) |
--promptForNonLocalDatabase=<true or false> | 提示非本地主機數據庫。(默認值:false) |
--currentDateTimeFunction=<value> | 覆蓋SQL中使用的當前日期時間函數。適用於不受支持的數據庫。 |
--logLevel=<level> | 執行日誌級別((debug, info, warning, severe, off)。 |
--help | 輸出命令行參數幫助。 |
--exportDataDir | 將保留insert語句csv文件的目錄(generateChangeLog命令所需)。 |
--propertyProviderClass=<properties.ClassName> | 要使用的自定義Properties實現 |
命令 | 描述 |
---|---|
--referenceUsername=<value> | 基礎數據庫用戶名 |
--referencePassword=<value> | 基礎數據庫密碼。 |
--referenceUrl=<value> | 基礎數據庫URL。 |
命令 | 描述 |
---|---|
--referenceDriver=<jdbc.driver.ClassName> | 基礎數據庫驅動程序類名。 |
命令 | 描述 |
---|---|
-D<property.name>=<property.value> | 傳遞名稱/值對以替換更改日誌中的$ {}塊。 |
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' } }
升級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在全部數據庫上運行以計算新的校驗和。