1. 概述
Liquibase 是一款强大的数据库管理工具,为跟踪和部署数据库变更提供了便捷方式。它还允许通过元数据(metadata)来描述变更集(changeset)在部署时的行为。
Preconditions 是一种特殊的元数据,主要用于控制变更日志(changelog)或单个变更集的执行。它们确保在继续部署前满足特定要求。
本文将深入探讨 Preconditions,学习如何定义条件逻辑和错误处理,以优化变更集在部署时的控制。
2. 理解 Preconditions
Preconditions 是 Liquibase 的核心功能,允许我们定义应用变更前必须满足的条件。可以把它们看作数据库变更的"守门人"。
在部署变更集前,Liquibase 会系统性地评估这些条件。如果条件不满足,Liquibase 会阻止变更集执行。根据 Preconditions 的配置,可能抛出失败、警告或直接跳过变更集。这种机制确保变更仅在数据库状态符合要求时应用,从而实现部署的严格管控,最大限度降低风险。
以下是 Preconditions 的典型应用场景:
✅ 数据库兼容性控制
限制变更仅在特定数据库类型、类型组或特定版本上执行,确保兼容性。
✅ 对象存在性检查
在创建或修改数据库对象前检查其是否已存在,避免意外错误。
⚠️ 数据安全校验
执行不可逆操作前检查现有数据。例如:当表仍包含数据时阻止删除操作(dropTable)。
✅ 数据完整性验证
变更前验证特定数据是否存在。例如:向配置表插入新行前确保该行不存在,避免重复或不一致。
这些实践能有效保障部署安全,提前规避潜在问题。
3. 定义 Preconditions 语法
Preconditions 是直接添加到变更日志文件中的标签,用于控制变更执行。下面详解其语法。
3.1. 基本语法
从简单示例开始:
<preConditions>
<dbms type="mysql,oracle" />
</preConditions>
这里使用 dbms
precondition,确保数据库是 MySQL 或 Oracle。
Liquibase 提供多种 precondition 标签满足不同需求:
标签名 | 功能 |
---|---|
dbms |
检查数据库类型 |
runningAs |
验证数据库用户名 |
tableExists |
检查表是否存在 |
columnExists |
确保特定列存在 |
foreignKeyExists |
检查外键是否存在 |
sqlCheck |
执行 SQL 查询并比对预期结果 |
这些 preconditions 可在 XML、YAML 或 JSON 格式的变更日志中定义。在 Liquibase 4.27.0 之前,只有 sqlCheck
支持格式化 SQL。**从 4.27.0 版本 起,tableExists
和 viewExists
等 preconditions 也支持 SQL 变更日志**:
--precondition-table-exists table:users
这对偏好使用 SQL 变更日志的用户是重大改进。
完整可用 preconditions 列表及属性可参考官方文档。
3.2. 嵌套 Preconditions
Liquibase 允许使用逻辑标签(如 <and>
、<or>
、<not>
)嵌套或组合多个 preconditions,创建复杂条件。
示例:
<preConditions>
<or>
<and>
<dbms type="oracle" />
<runningAs username="baeldung" />
</and>
<and>
<dbms type="mysql" />
<runningAs username="baeldung" />
</and>
</or>
</preConditions>
此例检查:数据库是 Oracle 且用户是 baeldung
,或数据库是 MySQL 且用户是 baeldung
。
未指定逻辑运算符时,Liquibase 默认使用 AND
逻辑:
<preConditions>
<tableExists tableName="users"/>
<columnExists tableName="users" columnName="email"/>
</preConditions>
此时需同时满足:users
表存在且包含 email
列。
Liquibase 对 preconditions 采用惰性求值:<and>
中首个条件失败时,后续条件不再检查;<or>
中首个条件满足时,剩余条件直接跳过。
4. 全局 vs 局部 Preconditions
Preconditions 可在两个层级使用:变更日志全局级别和变更集局部级别。
全局 Preconditions 定义在变更日志顶部(所有变更集之前),适用于所有变更集,适合通用检查(如数据库兼容性):
<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-latest.xsd">
<preConditions>
<dbms type="mysql"/>
<runningAs username="baeldung"/>
<sqlCheck expectedResult="9.1.0">
SELECT @@version;
</sqlCheck>
</preConditions>
<changeSet id="BAEL-1000" author="baeldung">
<addColumn tableName="users">
<column name="country" type="varchar(25)"/>
</addColumn>
</changeSet>
<changeSet id="BAEL-1001" author="baeldung">
<addColumn tableName="tutorials">
<column name="code" type="varchar(5)"/>
</addColumn>
</changeSet>
</databaseChangeLog>
此例检查:数据库是 MySQL、部署用户是 baeldung
、数据库版本为 9.1.0。任一条件不满足时,所有变更集都不会执行。
局部 Preconditions 定义在变更集顶部,仅作用于该变更集,适合针对性检查(如表/列存在性验证):
<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-latest.xsd">
<changeSet id="BAEL-1000" author="baeldung">
<addColumn tableName="users">
<column name="country" type="varchar(25)"/>
</addColumn>
</changeSet>
<changeSet id="BAEL-1002" author="baeldung">
<preConditions>
<tableExists tableName="users"/>
<columnExists tableName="users" columnName="last_visit"/>
</preConditions>
<dropColumn tableName="users" columnName="last_visit"/>
</changeSet>
</databaseChangeLog>
此 precondition 仅作用于 BAEL-1002
变更集,确保 users
表和 last_visit
列存在后才允许删除列。
5. 处理 Preconditions 失败与错误
Liquibase 区分 precondition 的失败(failure)和错误(error):
- 失败:precondition 成功执行但未满足预期条件
- 错误:技术问题(如语法错误)导致 precondition 无法执行
通过 onFail
和 onError
属性指定失败/错误时的处理方式。
5.1. onFail
与 onError
属性
示例变更集:
<changeSet id="BAEL-1003" author="baeldung">
<preConditions onFail="HALT" onError="HALT">
<not>
<columnExists tableName="users" columnName="verified"/>
</not>
</preConditions>
<addColumn tableName="users">
<column name="verified" type="boolean" defaultValue="false"/>
</addColumn>
</changeSet>
通过 mvn liquibase:update
执行。
若 verified
列已存在,precondition 失败并输出:
Not precondition failed
若 users
表不存在,则发生错误:
liquibase.exception.DatabaseException: Table 'baeldung_liquibase.users' doesn't exist
两种情况下的 HALT
值都会立即停止执行。
5.2. onFail
与 onError
的可选值
可配置的行为选项:
值 | 行为 | 适用场景 |
---|---|---|
HALT |
立即停止变更日志执行 | 默认行为 |
WARN |
记录警告但继续执行 | 非关键性检查 |
MARK_RAN |
标记变更集为已执行(写入 DATABASECHANGELOG 表)并继续 | 需跳过但记录执行状态 |
CONTINUE |
跳过当前变更集并继续执行后续 | 局部失败不影响全局 |
全局与局部 Preconditions 的属性支持差异:
属性 | 全局 Preconditions | 局部 Preconditions |
---|---|---|
onFail |
HALT , WARN |
HALT , CONTINUE , MARK_RAN , WARN |
onError |
HALT , WARN |
HALT , CONTINUE , MARK_RAN , WARN |
可通过 onErrorMessage
和 onFailMessage
自定义错误/失败消息:
<preConditions onFail="WARN" onFailMessage="Column verified already exists">
<not>
<columnExists tableName="users" columnName="verified"/>
</not>
</preConditions>
这些属性提供了灵活的 precondition 处理机制。
6. 总结
本文深入探讨了 Liquibase Preconditions 如何提升数据库更新的控制精度。它们确保在应用变更前数据库满足特定条件,有效降低风险并避免潜在错误。
通过合理运用 Preconditions,我们能够:
- 简化部署流程
- 保障数据库完整性
- 实现条件化变更执行
- 增强跨环境兼容性
掌握 Preconditions 是高效管理数据库变更的关键技能,尤其适合复杂生产环境下的数据库版本控制。