作者丨Rafal Grzegorczyk
译者丨陈骏
策划丨孙淑娟
【51CTO.com原创稿件】您是否还在手动对数据库执行各种脚本?您是否还在浪费时间去验证数据库脚本的正确性?您是否还需要将脚本合并到某个文件中,以便在每个环境中执行?在面对部署错误时,您是否需要花费数小时去查看数据库的更改,以定位原因?
如今,大多数组织都已经在其应用程序中实施了DevOps的CI/CD流程。不过,其数据库的自动化改造似乎尚未跟上时代。为此,我将向您介绍一种能够实现自动化脚本部署的数据库产品--Liquibase。
下面,我将向您展示如何使用Liquibase和Git在Pretius上,自动化数据库的更改过程。
Liquibase(简称LB)是一个用JAVA编写的开源工具。它以用户熟悉的格式定义了数据库接口,并能够自动生成特定于数据库的SQL。例如,它将数据库的更改(每一次更改可称为一个更改集)放入被称为changelog的文件中进行管理。通常,Liquibase在数据库架构中会自动创建两张表:
我将在下面示例中,基于SQL编写变更集,以实现对Oracle数据库的自动化更改过程。
请通过链接
https://www.liquibase.org/download,选择“仅文件(Just the files)”的方式,下载Liquibase的最新版本。在本文中,我将使用版本:4.3.0 build 09.02.2021。
在将其zip文件夹解压缩后,您必须将新的路径变量(New Path System Variable)设置为计算机上的liquibase-version#bin文件夹。同时,为了使Liquibase正常工作,您还必须安装Java。
通过在CLI工具(在此,我使用的是Visual Studio Code)输入:Liquibase—version,您将能看到:
如果您在文件中使用的是UTF8编码,那么请务必在liquibase.bat文件中添加一行:IF NOT DEFINED JAVA_OPTS set JAVA_OPTS=-Dfile.encoding=UTF–8。
下面,让我们来组织各个文件(在本例中,我的GIT存储库放在文件夹HR中)。在各个文件夹中,我们可以在项目开发的过程中创建不同的文件。如果您有其他类型的对象(如“创建或替换”类型),那么只需要用它们创建“同义(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文件夹结构
现在,我们创建一个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数据库托管在Oracle自治数据库上,那么就需要使用Wallet,通过Liquibase去连接它。为此,请下载Wallet并记住其密码。
请将您的WALLET_NAME.ZIP解压到之前创建的HR/Liquibase文件夹中,并编辑
HR/liquibase/wallet_name/ojdbc.properties文件:
更改ojdsb.properties
修改后的文件如上图所示。在
javax.NET.ssl.trustStorePassword和javax.net.ssl.keyStorePassword行,你可以设置ATP Wallet的密码。
在
liquibase_local.properties文件中,请编辑URL一行,并设置连接的名称(即,来自Wallet/tnsnames.ora,以及去往Wallet的路径):
url: jdbc:oracle:thin:@rgatp28_high?TNS_ADMIN=liquibase/Wallet_RGATP28
当然,请检查您的sqlnet.ora文件,确保其“SSL_SERVER_DN_MATCH=yes”,且无需改变其他地方。
如果一切设置正确,我们便可以顺利连接上DEV数据库。让我们从HR文件夹(Liquibase的属性文件位置)处启动CLI,并输入:
Liquibase—defaultsFile=liquibase_dev.properties updateSQL
VSCode终端中的updateSQL命令
其中:
几秒钟后,LB将会生成output_file.sql:
生成的output_file.sql
如前所述,如果您在数据库中运行该脚本,它将创建两个表:DATABASECHANGELOG和DATABASECHANGELOGLOCK。下面,让我们通过Liquibase—defaultsFile=liquibase_dev.properties update,来创建这些表。其中的update命令是对数据库执行SQL语句。完成后,您将看到如下结构:
我们需要创建一个changelog文件,并指向包含对象的文件夹。在此,我创建了如下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>
它指向对象文件夹、及其所有内容。为了将主changelog文件HR/liquibaseupdate.xml设置为指向master.xml文件的路径,您只需添一行:
<include file="./master.xml"/>
在update.xml中的include file="./master.xml"
由于Liquibase始终会从Liquibase_dev.properties文件和update.xml文件处运行,因此我们需要让它能够“看到”所有的文件。
我们需要为DML和DDL类型的更改创建一个单独的changelog文件,并将更改集写入其中。为此,我们只需创建一个changelog.sql文件,并输入如下内容,以将其标记为LiquibaseSQL文件:
—Liquibaseformatted sql
将changelog.sql标记为LiquibaseSQL文件
我们通过在master.xml文件中添加如下内容,以指向新的changelog:
指向新的changelog
指向changelog或文件夹的顺序是非常重要的。它需要告知Liquibase在运行SQL时的顺序。我们最好先运行changelog(其中包含了“create table(...)”),然后再运行使用该表的编译包。
下面,让我们在变更集中创建第一个项目表:
—changeset AUTHOR:CHANGESET_NAME —comment OPTIONAL COMMENT YOUR DDL
创建第一个项目表
为了预览到数据库有哪些更改,我们让LB生成对应的SQL文件。
Liquibase—defaultsFile=liquibase_dev.properties updateSQL
由Liquibase生成SQL文件
您可能注意到了,LB通过设置LOCKED = 1,来锁定DATABASECHANGELOGLOCK表。也就是说,当您将脚本运行到DB时,列LOCKED被设置为1。而当另一个用户同时运行LB时,Liquibase将为此等待,直到锁定被放开,再创建一个SHOES表,将日志的更改插入到DATABASECHANGELOG中,并从DATABASECHANGELOGLOCK表中释放掉已有的锁。
如果一切正常,如下脚本会被执行到数据库中:
Liquibase—defaultsFile=liquibase_dev.properties update
接着,表SHOES会被创建出来。
我们也可以查询到谁、为何、以及何时创建了这张表。
我们也可以如法创建其他脚本。在此,我通过2个单独的文件,创建了一个SHOES_PKG包。每个文件都是带有附加参数的唯一变更集,并被标记为Liquibase格式的SQL文件。
SHOES_BODY和SHOES_SPEC SQL文件
其中:
因此,LB在对数据库进行updateSQL操作时,就会去编译包的规范(package spec)、以及包的主体(package body)。一旦我们在数据库中通过update命令编译这些包,它们都会被记录下来。
通过查看MD5SUM的列值可知,它是变更集的最后一次校验和。也就是说,运行了updateSQL后,所有前期被“挂起”的更改都被执行,而且除了锁定LB表外,LB不会在SQL中生成任何内容。
运用updateSQL检查output_local.sql
现在,让我们改变SHOES_PKG本身,并保存该文件。
更新SHOES_PKG本身
那么该文件的校验和会发生变化,LB将再次编译这个包,并运行更新。
Liquidbase中的更新
数据库中的更新
Liquibase将再次编译这个包,并使用DATABASECHANGELOG表中的实际DATEEXECUTED和新的MD5SUM等变更集,去更新相应的行。
虽然我们好几种方法可以让Liquibase为现有的数据库实现自动化,但是我在此只向您展示最实用的两种。您可以从中选择最适合实际需求的一种。
我们通过在项目的存储库中配置Liquibase,并保留所有文件的基础上,在master.xml文件中添加指向它们的路径。具体而言,在实施Liquibase之前,我创建了2个过程和2个触发器:
P_ADD_JOB_HISTORY P_SECURE_DML TRG_SECURE_EMPLOYEES TRG_UPDATE_JOB_HISTORY
现有的P_ADD_JOB_HISTORY.sql文件
您并不需要将“changeset”或“–Liquibaseformatted sql”添加到文件中。
更新后master.xml中的文件路径
我在自己的master.xml中添加了一个指向PROCEDURES文件夹的路径。
下面,让我们运行LiquibaseupdateSQL,并查看Liquibase会执行什么样的SQL:
Liquibase—defaultsFile=liquibase_dev.properties updateSQL
首次尝试更新SQL
既然我们的数据库中已经有了这些过程和触发器,我们就需要通过ChangelogSync和ChangelogSyncSQL命令,避免重复创建。让我们运行ChangelogSyncSQL,并查看其结果。
Liquibase—defaultsFile=liquibase_dev.properties ChangelogSyncSQL
输出的SQL文件为:
可见,SQL文件只插入了一个DATABASECHANGELOG表。它会告知Liquibase这些对象已经创建好了,不需要再次运行。现在,我们便可以将其插入到Oracle数据库中了:
Liquibase—defaultsFile=liquibase_dev.properties ChangelogSync
此时,在DATABASECHANGELOG表中会有4个新的变更集:
您也许会问,这些奇怪的“raw”ID 是什么?为什么作者又被称为“includeAll”呢?这是因为我们采取了最简单、最快捷的方式,将现有的项目迁移到了Liquibase处,而这些变更集是被自动创建的。
当然,您也可以进行一些更改。例如,在P_ADD_JOB_HISTORY中,只需添加一个changeset,就像您在创建新数据库对象时常做的那样。
更改P_ADD_JOB_HISTORY
然后运行Liquibase的更新命令:
现在,Changeset就带有了合适的作者、ID等信息。
在上面的示例中,我向您展示了添加现有对象(可创建或替换)的简单方法,且无需手动创建变更集。我认为这是将Liquibase安装到拥有数百个对象的、现有数据库中的最佳方式。不过,当涉及到库里有不能被替换的对象(如表格)时,我们需要使用另一种方式。
正如前面所描述过的,请添加对象,并在master.xml文件中记下文件夹的路径。接着请运行ChangelogSync,并让Liquibase自动创建raw/includeAll/filename的变更集。
由Liquibase生成的变更集
当然,您也可以采用更好的方法,为每个文件创建一个变更集,如下图所示:
虽然这会需要更多的工作,但是您可以在日志中获得更全面的信息:
针对无法创建或替换的对象,您同样有两种方法:
在此,我们着重讨论第二种方式。由于在实施Liquibase之前我已创建好了EMPLOYEES和JOBS两张表,因此我会在新的文件夹HR/scripts_before_Liquibase中,创建changelog_ddl.sql和changelog_constraints.sql两个changelog文件。此外,我也创建了另一个scripts_before_liquibase.xml文件,并将其指向这两个changelog。其中的“include file”通过优先级的方式,告知Liquibase运行脚本的顺序,即:首先创建表,然后创建约束和索引。
新的scripts_before_liquibase.xml文件
这两个文件能够方便您在表中创建ref_constraint时,避免产生冲突。如下图所示,请记住在master.xml文件中,将路径添加到新创建的XML文件(即
HR/script_before_liquibase/scripts_before_liquibase.xml)中。
下面是为各种表和约束创建的变更集。
在添加了所有的变更集之后,我们将它们标记为已执行的状态。
让我们运行ChangelogSyncSQL来进行预览,并让ChangelogSync对数据库执行SQL。
运行ChangeSyncSQL和ChangelogSync
下图展示了ChangelogSync命令执行后,更新了的数据库。至此,我们已大功告成,您也可以选用自己喜欢的方式,通过Liquibase来实现数据库的自动化。
如您所见,通过使用Liquibase,我们可以在数据库更改的发布过程中,跟踪所有的相关内容。其中,需要开发人员遵守如下流程:
【51CTO原创稿件,合作站点转载请注明原文作者和出处为51CTO.com】