重新定义在包含文件中仍然可见的本地 liquibase 属性

redefine local liquibase properies that are still visible in included files

我的一些 table 实现了需要特定列的概念。我想避免将这些列的变更集复制到所有相关的 table,而是重用一个模板来创建实际的变更集。

根据我在网上找到的不同想法,我尝试了这个:

db/templates/0004-create-validity-period-data-template.xml

<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<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="../templates/0002-create-versioned-data-template.xml" relativeToChangelogFile="true" />

    <changeSet author="${table.author}" id="Create ValidityPeriodData for ${table.name}" >
        <comment>Create columns of super class ValidityPeriodData for table ${table.name}</comment>
        <addColumn tableName="${table.name}" >
            <column name="valid_from" type="date" remarks="the earliest point in time when this entry is valid" />
            <column name="valid_until" type="date" remarks="the last point in time when this entry is valid" />
        </addColumn>
    </changeSet>

</databaseChangeLog>

db/changelog/0017-create-credential-table.xml

<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<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">

    <property name="table.name" value="login" />
    <property name="table.author" value="guess ;-)" />

    <include file="../templates/0004-create-validity-period-data-template.xml" relativeToChangelogFile="true" />

    <changeSet author="${table.author}" id="Create Credential table">
        <addColumn tableName="${table.name}">
            <column name="login_id" type="${uuid_type}"/>
            <column name="password" type="VARCHAR(255)">
                <constraints nullable="false"/>
            </column>
            <column name="salt" type="VARCHAR(128)"/>
        </addColumn>
        <addForeignKeyConstraint baseColumnNames="login_id"
                                 baseTableName="${table.name}"
                                 constraintName="fk_credential_of_login"
                                 onDelete="RESTRICT"
                                 onUpdate="RESTRICT"
                                 referencedColumnNames="id"
                                 referencedTableName="login"/>
    </changeSet>

</databaseChangeLog>

问题

属性(table.name、table.author)默认为global="true",这意味着它们在所有变更集文件中都可见,但不能overwritten/redefined。所以这只适用于单个 table,这违背了目的。 如果我将它们声明为 global="false",我可以为每个需要这些列的 table 重新定义它们,但是现在包含的文件看不到这些属性。

问题

有没有办法重新定义或覆盖不同变更集文件的属性(就像 global="false" 所做的那样),但仍然让它们在包含的文件中以递归方式表现得像 global="true"

我认为您对 liquibase changeSets 的用法有误解。

ChangeSet 应该是原子的并遵循 ​​"one action - one changeSet" 概念。它不应该是某种 "reusable function"。这种方法可能会损坏您的数据库模式。因此,您不会删除旧的 changeSets

所以我建议您为每个 table 需要添加列的地方编写变更集。

另外,看看这篇文章:Trimming ChangeLog Files

背景

我看过liquibase的源代码。事实证明当前的实现(在版本 3.6.2、3.6.3、3.8.0 中)有点奇怪。

private ChangeLogParameter findParameter(String key, DatabaseChangeLog changeLog) {
    ChangeLogParameter result = null;

    List<ChangeLogParameter> found = new ArrayList<>();
    for (ChangeLogParameter param : changeLogParameters) {
        if (param.getKey().equalsIgnoreCase(key) && param.isValid()) {
            found.add(param);
        }
    }
    
    if (found.size() == 1) {
        // this case is typically a global param, but could also be a unique non-global param in one specific
        // changelog
        result = found.get(0);
    } else if (found.size() > 1) {
        for (ChangeLogParameter changeLogParameter : found) {
            if (changeLogParameter.getChangeLog().equals(changeLog)) {
                result = changeLogParameter;
            }
        }
    }
    
    return result;
}

您还需要知道表达式会在解析 changeSet 后立即展开。 changeSets 中定义的参数尚未读取,不能考虑。

所以第一次遇到一个参数时,它实际上被视为global=true,不管是否是这种情况。

不会记录第二个同名的全局参数。但是,将记录第二个本地参数。

一旦参数列表包含第二个具有相同名称的 ChangeLogParameter,它们将以不同方式展开。现在它会查找在同一个变更集文件中定义的该参数的最后定义版本 - 但无论是全局还是本地都没有关系。


回答

所以回答我自己的问题。 liquibase 的当前实现不支持那种东西。最重要的是,它在扩展表达式方面表现得有些不一致。


解决方案?

我在 GitHub 上用 liquibase CORE-3493 and provided a possible solution 提交了错误报告。

编辑: 此更改已包含在版本 3.10.2 中。但是请注意,我在不知不觉中引入了一项重大更改 issue #1293。这对新项目无关紧要,但如果已经使用了变量,则可能会破坏现有项目。问题中建议了解决方法。