一文詳解Liquibase如何自動(dòng)化數(shù)據(jù)庫(kù)腳本部署
譯文【51CTO.com快譯】您是否還在手動(dòng)對(duì)數(shù)據(jù)庫(kù)執(zhí)行各種腳本?您是否還在浪費(fèi)時(shí)間去驗(yàn)證數(shù)據(jù)庫(kù)腳本的正確性?您是否還需要將腳本合并到某個(gè)文件中,以便在每個(gè)環(huán)境中執(zhí)行?在面對(duì)部署錯(cuò)誤時(shí),您是否需要花費(fèi)數(shù)小時(shí)去查看數(shù)據(jù)庫(kù)的更改,以定位原因?
如今,大多數(shù)組織都已經(jīng)在其應(yīng)用程序中實(shí)施了DevOps的CI/CD流程。不過(guò),其數(shù)據(jù)庫(kù)的自動(dòng)化改造似乎尚未跟上時(shí)代。為此,我將向您介紹一種能夠?qū)崿F(xiàn)自動(dòng)化腳本部署的數(shù)據(jù)庫(kù)產(chǎn)品--Liquibase。
Liquibase的基本特點(diǎn)
- 自動(dòng)化數(shù)據(jù)庫(kù)的部署腳本。
- 以相同的方式部署到不同的環(huán)境中。
- 能夠?yàn)槊看螖?shù)據(jù)庫(kù)的更改準(zhǔn)備好回滾。
- 能夠?qū)⒉渴鸬乃性敿?xì)信息集中到一處。
- 最少化的部署錯(cuò)誤。
- 方便開(kāi)發(fā)人員針對(duì)相同的數(shù)據(jù)庫(kù),進(jìn)行高效的協(xié)同編程。
- 審核逐個(gè)更改(請(qǐng)參考下圖)。
下面,我將向您展示如何使用Liquibase和Git在Pretius上,自動(dòng)化數(shù)據(jù)庫(kù)的更改過(guò)程。
什么是Liquibase?
Liquibase(簡(jiǎn)稱LB)是一個(gè)用Java編寫的開(kāi)源工具。它以用戶熟悉的格式定義了數(shù)據(jù)庫(kù)接口,并能夠自動(dòng)生成特定于數(shù)據(jù)庫(kù)的SQL。例如,它將數(shù)據(jù)庫(kù)的更改(每一次更改可稱為一個(gè)更改集)放入被稱為changelog的文件中進(jìn)行管理。通常,Liquibase在數(shù)據(jù)庫(kù)架構(gòu)中會(huì)自動(dòng)創(chuàng)建兩張表:
- DATABASECHANGELOG — 存儲(chǔ)有關(guān)數(shù)據(jù)庫(kù)所有更改信息的表。
- DATABASECHANGELOGLOCK — 用于防止用戶同時(shí)對(duì)數(shù)據(jù)庫(kù)進(jìn)行更改。
我將在下面示例中,基于SQL編寫變更集,以實(shí)現(xiàn)對(duì)Oracle數(shù)據(jù)庫(kù)的自動(dòng)化更改過(guò)程。
從安裝Liquibase開(kāi)始
請(qǐng)通過(guò)鏈接https://www.liquibase.org/download,選擇“僅文件(Just the files)”的方式,下載Liquibase的最新版本。在本文中,我將使用版本:4.3.0 build 09.02.2021。
在將其zip文件夾解壓縮后,您必須將新的路徑變量(New Path System Variable)設(shè)置為計(jì)算機(jī)上的liquibase-version#bin文件夾。同時(shí),為了使Liquibase正常工作,您還必須安裝Java。
通過(guò)在CLI工具(在此,我使用的是Visual Studio Code)輸入:Liquibase—version,您將能看到:
如果您在文件中使用的是UTF8編碼,那么請(qǐng)務(wù)必在liquibase.bat文件中添加一行:IF NOT DEFINED JAVA_OPTS set JAVA_OPTS=-Dfile.encoding=UTF–8。
配置項(xiàng)目和Liquibase
下面,讓我們來(lái)組織各個(gè)文件(在本例中,我的GIT存儲(chǔ)庫(kù)放在文件夾HR中)。在各個(gè)文件夾中,我們可以在項(xiàng)目開(kāi)發(fā)的過(guò)程中創(chuàng)建不同的文件。如果您有其他類型的對(duì)象(如“創(chuàng)建或替換”類型),那么只需要用它們創(chuàng)建“同義(synonyms)”文件夾即可。
Liquibase中的文件組織
- #path to our master changelog file
- changeLogFile:Liquibase/update.xml
- #dbhost and credentials
- url: jdbc:oracle:thin:@127.0.0.1:1521/XEPDB1
- username: HR
- password: XXXXXX
- #OJDBC driver localization
- classpath:Liquibase/ojdbc8.jar
- #schema, whereLiquibasewill store it’s DATABASECHANGELOG and DATABASECHANGELOGLOCK table(if other than HR, remember to add grants to HR!)
- liquibaseSchemaName: HR
- #default SQL file name generated byLiquibase
- outputFile=output_local.sql
- #debug mode
- loglevel=SEVERE
- #extra option fromLiquibase, we don’t need it for now.
- liquibase.hub.mode=off
更新了的Liquibase文件夾結(jié)構(gòu)
現(xiàn)在,我們創(chuàng)建一個(gè)update.xml文件,并將它放入帶有OJDBC文件的、新的 hr/Liquibase文件夾中:
- <?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-4.3.xsd"></databaseChangeLog>
使用 Oracle Wallet(可選)
如果您的Oracle數(shù)據(jù)庫(kù)托管在Oracle自治數(shù)據(jù)庫(kù)上,那么就需要使用Wallet,通過(guò)Liquibase去連接它。為此,請(qǐng)下載Wallet并記住其密碼。
請(qǐng)將您的WALLET_NAME.ZIP解壓到之前創(chuàng)建的HR/Liquibase文件夾中,并編輯HR/liquibase/wallet_name/ojdbc.properties文件:
更改ojdsb.properties
修改后的文件如上圖所示。在javax.net.ssl.trustStorePassword和javax.net.ssl.keyStorePassword行,你可以設(shè)置ATP Wallet的密碼。
在liquibase_local.properties文件中,請(qǐng)編輯URL一行,并設(shè)置連接的名稱(即,來(lái)自Wallet/tnsnames.ora,以及去往Wallet的路徑):
- url: jdbc:oracle:thin:@rgatp28_high?TNS_ADMIN=liquibase/Wallet_RGATP28
當(dāng)然,請(qǐng)檢查您的sqlnet.ora文件,確保其“SSL_SERVER_DN_MATCH=yes”,且無(wú)需改變其他地方。
將Liquibase與數(shù)據(jù)庫(kù)連接
如果一切設(shè)置正確,我們便可以順利連接上DEV數(shù)據(jù)庫(kù)。讓我們從HR文件夾(Liquibase的屬性文件位置)處啟動(dòng)CLI,并輸入:
- Liquibase—defaultsFile=liquibase_dev.properties updateSQL
VSCode終端中的updateSQL命令
其中:
- Liquibase會(huì)調(diào)用LB的環(huán)境路徑。
- defaultsFile指定屬性文件的名稱和位置。如果您將屬性文件命名為“liquibase.properties”,那么可以省略此命令。
- updateSQL負(fù)責(zé)生成SQL腳本(它并不會(huì)對(duì)數(shù)據(jù)庫(kù)執(zhí)行任何操作)。
幾秒鐘后,LB將會(huì)生成output_file.sql:
生成的output_file.sql
如前所述,如果您在數(shù)據(jù)庫(kù)中運(yùn)行該腳本,它將創(chuàng)建兩個(gè)表:DATABASECHANGELOG和DATABASECHANGELOGLOCK。下面,讓我們通過(guò)Liquibase—defaultsFile=liquibase_dev.properties update,來(lái)創(chuàng)建這些表。其中的update命令是對(duì)數(shù)據(jù)庫(kù)執(zhí)行SQL語(yǔ)句。完成后,您將看到如下結(jié)構(gòu):
我們需要?jiǎng)?chuàng)建一個(gè)changelog文件,并指向包含對(duì)象的文件夾。在此,我創(chuàng)建了如下HR/master.xml文件:
- <?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-4.3.xsd">
- <includeAll path="triggers" relativeToChangelogFile="true"/>
- <includeAll path="views" relativeToChangelogFile="true"/>
- <includeAll path="types" relativeToChangelogFile="true"/>
- <includeAll path="package_spec" relativeToChangelogFile="true"/>
- <includeAll path="package_bodies" relativeToChangelogFile="true"/>
- </databaseChangeLog>
它指向?qū)ο笪募A、及其所有內(nèi)容。為了將主changelog文件HR/liquibaseupdate.xml設(shè)置為指向master.xml文件的路徑,您只需添一行:
- <include file="./master.xml"/>
在update.xml中的include file="./master.xml"
由于Liquibase始終會(huì)從Liquibase_dev.properties文件和update.xml文件處運(yùn)行,因此我們需要讓它能夠“看到”所有的文件。
跟蹤DML和DDL數(shù)據(jù)庫(kù)的更改
我們需要為DML和DDL類型的更改創(chuàng)建一個(gè)單獨(dú)的changelog文件,并將更改集寫入其中。為此,我們只需創(chuàng)建一個(gè)changelog.sql文件,并輸入如下內(nèi)容,以將其標(biāo)記為L(zhǎng)iquibaseSQL文件:
- —Liquibaseformatted sql
將changelog.sql標(biāo)記為L(zhǎng)iquibaseSQL文件
我們通過(guò)在master.xml文件中添加如下內(nèi)容,以指向新的changelog:
指向新的changelog
指向changelog或文件夾的順序是非常重要的。它需要告知Liquibase在運(yùn)行SQL時(shí)的順序。我們最好先運(yùn)行changelog(其中包含了“create table(...)”),然后再運(yùn)行使用該表的編譯包。
下面,讓我們?cè)谧兏袆?chuàng)建第一個(gè)項(xiàng)目表:
- —changeset AUTHOR:CHANGESET_NAME
- —comment OPTIONAL COMMENT
- YOUR DDL
創(chuàng)建第一個(gè)項(xiàng)目表
為了預(yù)覽到數(shù)據(jù)庫(kù)有哪些更改,我們讓LB生成對(duì)應(yīng)的SQL文件。
- Liquibase—defaultsFile=liquibase_dev.properties updateSQL
由Liquibase生成SQL文件
您可能注意到了,LB通過(guò)設(shè)置LOCKED = 1,來(lái)鎖定DATABASECHANGELOGLOCK表。也就是說(shuō),當(dāng)您將腳本運(yùn)行到DB時(shí),列LOCKED被設(shè)置為1。而當(dāng)另一個(gè)用戶同時(shí)運(yùn)行LB時(shí),Liquibase將為此等待,直到鎖定被放開(kāi),再創(chuàng)建一個(gè)SHOES表,將日志的更改插入到DATABASECHANGELOG中,并從DATABASECHANGELOGLOCK表中釋放掉已有的鎖。
如果一切正常,如下腳本會(huì)被執(zhí)行到數(shù)據(jù)庫(kù)中:
- Liquibase—defaultsFile=liquibase_dev.properties update
接著,表SHOES會(huì)被創(chuàng)建出來(lái)。
我們也可以查詢到誰(shuí)、為何、以及何時(shí)創(chuàng)建了這張表。
跟蹤包、視圖等其他更改
我們也可以如法創(chuàng)建其他腳本。在此,我通過(guò)2個(gè)單獨(dú)的文件,創(chuàng)建了一個(gè)SHOES_PKG包。每個(gè)文件都是帶有附加參數(shù)的唯一變更集,并被標(biāo)記為L(zhǎng)iquibase格式的SQL文件。
SHOES_BODY和SHOES_SPEC SQL文件
其中:
- runOnChange:true ——意味著每次更改包時(shí),Liquibase都會(huì)針對(duì)數(shù)據(jù)庫(kù)運(yùn)行該變更集,也就是編譯這個(gè)包。
- stripComments:false ——意味著不要去除代碼注釋。
因此,LB在對(duì)數(shù)據(jù)庫(kù)進(jìn)行updateSQL操作時(shí),就會(huì)去編譯包的規(guī)范(package spec)、以及包的主體(package body)。一旦我們?cè)跀?shù)據(jù)庫(kù)中通過(guò)update命令編譯這些包,它們都會(huì)被記錄下來(lái)。
通過(guò)查看MD5SUM的列值可知,它是變更集的最后一次校驗(yàn)和。也就是說(shuō),運(yùn)行了updateSQL后,所有前期被“掛起”的更改都被執(zhí)行,而且除了鎖定LB表外,LB不會(huì)在SQL中生成任何內(nèi)容。
運(yùn)用updateSQL檢查output_local.sql
現(xiàn)在,讓我們改變SHOES_PKG本身,并保存該文件。
更新SHOES_PKG本身
那么該文件的校驗(yàn)和會(huì)發(fā)生變化,LB將再次編譯這個(gè)包,并運(yùn)行更新。
Liquidbase中的更新
數(shù)據(jù)庫(kù)中的更新
Liquibase將再次編譯這個(gè)包,并使用DATABASECHANGELOG表中的實(shí)際DATEEXECUTED和新的MD5SUM等變更集,去更新相應(yīng)的行。
如何在現(xiàn)有軟件項(xiàng)目中安裝Liquibase?
雖然我們好幾種方法可以讓Liquibase為現(xiàn)有的數(shù)據(jù)庫(kù)實(shí)現(xiàn)自動(dòng)化,但是我在此只向您展示最實(shí)用的兩種。您可以從中選擇最適合實(shí)際需求的一種。
當(dāng)現(xiàn)有的項(xiàng)目中有很多對(duì)象時(shí)
我們通過(guò)在項(xiàng)目的存儲(chǔ)庫(kù)中配置Liquibase,并保留所有文件的基礎(chǔ)上,在master.xml文件中添加指向它們的路徑。具體而言,在實(shí)施Liquibase之前,我創(chuàng)建了2個(gè)過(guò)程和2個(gè)觸發(fā)器:
- P_ADD_JOB_HISTORY
- P_SECURE_DML
- TRG_SECURE_EMPLOYEES
- TRG_UPDATE_JOB_HISTORY
現(xiàn)有的P_ADD_JOB_HISTORY.sql文件
您并不需要將“changeset”或“–Liquibaseformatted sql”添加到文件中。
更新后master.xml中的文件路徑
我在自己的master.xml中添加了一個(gè)指向PROCEDURES文件夾的路徑。
下面,讓我們運(yùn)行LiquibaseupdateSQL,并查看Liquibase會(huì)執(zhí)行什么樣的SQL:
- Liquibase—defaultsFile=liquibase_dev.properties updateSQL
首次嘗試更新SQL
既然我們的數(shù)據(jù)庫(kù)中已經(jīng)有了這些過(guò)程和觸發(fā)器,我們就需要通過(guò)ChangelogSync和ChangelogSyncSQL命令,避免重復(fù)創(chuàng)建。讓我們運(yùn)行ChangelogSyncSQL,并查看其結(jié)果。
- Liquibase—defaultsFile=liquibase_dev.properties ChangelogSyncSQL
輸出的SQL文件為:
可見(jiàn),SQL文件只插入了一個(gè)DATABASECHANGELOG表。它會(huì)告知Liquibase這些對(duì)象已經(jīng)創(chuàng)建好了,不需要再次運(yùn)行?,F(xiàn)在,我們便可以將其插入到Oracle數(shù)據(jù)庫(kù)中了:
- Liquibase—defaultsFile=liquibase_dev.properties ChangelogSync
此時(shí),在DATABASECHANGELOG表中會(huì)有4個(gè)新的變更集:
您也許會(huì)問(wèn),這些奇怪的“raw”ID 是什么?為什么作者又被稱為“includeAll”呢?這是因?yàn)槲覀儾扇×俗詈?jiǎn)單、最快捷的方式,將現(xiàn)有的項(xiàng)目遷移到了Liquibase處,而這些變更集是被自動(dòng)創(chuàng)建的。
當(dāng)然,您也可以進(jìn)行一些更改。例如,在P_ADD_JOB_HISTORY中,只需添加一個(gè)changeset,就像您在創(chuàng)建新數(shù)據(jù)庫(kù)對(duì)象時(shí)常做的那樣。
更改P_ADD_JOB_HISTORY
然后運(yùn)行Liquibase的更新命令:
現(xiàn)在,Changeset就帶有了合適的作者、ID等信息。
在上面的示例中,我向您展示了添加現(xiàn)有對(duì)象(可創(chuàng)建或替換)的簡(jiǎn)單方法,且無(wú)需手動(dòng)創(chuàng)建變更集。我認(rèn)為這是將Liquibase安裝到擁有數(shù)百個(gè)對(duì)象的、現(xiàn)有數(shù)據(jù)庫(kù)中的最佳方式。不過(guò),當(dāng)涉及到庫(kù)里有不能被替換的對(duì)象(如表格)時(shí),我們需要使用另一種方式。
當(dāng)現(xiàn)有項(xiàng)目中沒(méi)有很多對(duì)象時(shí)
創(chuàng)建或替換的對(duì)象
正如前面所描述過(guò)的,請(qǐng)?zhí)砑訉?duì)象,并在master.xml文件中記下文件夾的路徑。接著請(qǐng)運(yùn)行ChangelogSync,并讓Liquibase自動(dòng)創(chuàng)建raw/includeAll/filename的變更集。
由Liquibase生成的變更集
當(dāng)然,您也可以采用更好的方法,為每個(gè)文件創(chuàng)建一個(gè)變更集,如下圖所示:
雖然這會(huì)需要更多的工作,但是您可以在日志中獲得更全面的信息:
針對(duì)無(wú)法創(chuàng)建或替換的對(duì)象,您同樣有兩種方法:
- 對(duì)這些對(duì)象不做任何操作,但請(qǐng)記住始終為其中的每個(gè)更改(包括:更改表、刪除列等)創(chuàng)建變更集,并將其添加到changelog.sql文件中。
- 創(chuàng)建變更集并將它們標(biāo)記為過(guò)去已被執(zhí)行過(guò)。
在此,我們著重討論第二種方式。由于在實(shí)施Liquibase之前我已創(chuàng)建好了EMPLOYEES和JOBS兩張表,因此我會(huì)在新的文件夾HR/scripts_before_Liquibase中,創(chuàng)建changelog_ddl.sql和changelog_constraints.sql兩個(gè)changelog文件。此外,我也創(chuàng)建了另一個(gè)scripts_before_liquibase.xml文件,并將其指向這兩個(gè)changelog。其中的“include file”通過(guò)優(yōu)先級(jí)的方式,告知Liquibase運(yùn)行腳本的順序,即:首先創(chuàng)建表,然后創(chuàng)建約束和索引。
新的scripts_before_liquibase.xml文件
這兩個(gè)文件能夠方便您在表中創(chuàng)建ref_constraint時(shí),避免產(chǎn)生沖突。如下圖所示,請(qǐng)記住在master.xml文件中,將路徑添加到新創(chuàng)建的XML文件(即HR/script_before_liquibase/scripts_before_liquibase.xml)中。
下面是為各種表和約束創(chuàng)建的變更集。
在添加了所有的變更集之后,我們將它們標(biāo)記為已執(zhí)行的狀態(tài)。
讓我們運(yùn)行ChangelogSyncSQL來(lái)進(jìn)行預(yù)覽,并讓ChangelogSync對(duì)數(shù)據(jù)庫(kù)執(zhí)行SQL。
運(yùn)行ChangeSyncSQL和ChangelogSync
下圖展示了ChangelogSync命令執(zhí)行后,更新了的數(shù)據(jù)庫(kù)。至此,我們已大功告成,您也可以選用自己喜歡的方式,通過(guò)Liquibase來(lái)實(shí)現(xiàn)數(shù)據(jù)庫(kù)的自動(dòng)化。
小結(jié)
如您所見(jiàn),通過(guò)使用Liquibase,我們可以在數(shù)據(jù)庫(kù)更改的發(fā)布過(guò)程中,跟蹤所有的相關(guān)內(nèi)容。其中,需要開(kāi)發(fā)人員遵守如下流程:
- 基于變更集的唯一性,并結(jié)合AUTHOR:ID(task)與文件名(帶changelog的文件),將變更集添加到changelog中。而且,請(qǐng)不要在沒(méi)有Liquibase的情況下,更改任何內(nèi)容。
- 驗(yàn)證待執(zhí)行的SQL。
- 運(yùn)行數(shù)據(jù)庫(kù)的update命令(記住,應(yīng)當(dāng)先運(yùn)行updateSQL,再執(zhí)行update命令)。
- 通過(guò)檢查數(shù)據(jù)庫(kù)對(duì)象和DATABASECHANGELOG表,驗(yàn)證變更集是否已被執(zhí)行。
原文標(biāo)題:What is Liquibase? How to Automate Your Database Script Deployment,作者:Rafal Grzegorczyk
【51CTO譯稿,合作站點(diǎn)轉(zhuǎn)載請(qǐng)注明原文譯者和出處為51CTO.com】