4. ChangeLog 文件结构:四种格式对比与组织策略
ChangeLog 是 Liquibase 迁移的核心清单文件,它定义了数据库变更的顺序、作者、ID 以及变更集之间的依赖关系。Liquibase 原生支持 XML、YAML、JSON 和 SQL 四种格式,每种格式在可读性、类型安全、版本协作和动态参数化方面各有优劣。本章将系统对比四种格式的根元素、语法结构,并深入讲解 include 与 includeAll 的模块化组织策略。
4.1 四种格式的根元素与核心结构对比
无论使用哪种格式,ChangeLog 都必须包含一个根元素(或等效的顶层结构),用于声明命名空间、数据库变更逻辑(databaseChangeLog)以及版本信息。下表从根元素、变更集定义方式、类型检查、动态参数支持等维度进行对比:
| 对比维度 | XML | YAML | JSON | SQL |
|---|---|---|---|---|
| 根元素 | <databaseChangeLog> |
databaseChangeLog: |
{ "databaseChangeLog": [...] } |
无根元素,纯 SQL 语句 + Liquibase 注释指令 |
| 变更集定义 | <changeSet id="1" author="dev"> |
- changeSet: { id: 1, author: dev } |
{ "changeSet": { "id": 1, "author": "dev" } } |
-- liquibase formatted sql-- changeset dev:1 |
| 类型检查 | 强类型(XSD 校验) | 弱类型(缩进敏感) | 弱类型(引号敏感) | 无类型检查 |
| 动态参数 | 支持 ${property} |
支持 ${property} |
支持 ${property} |
支持 ${property} |
| IDE 支持 | 优秀(自动补全、校验) | 良好(部分 IDE 支持) | 良好(JSON Schema) | 一般(纯文本) |
| 可读性 | 中等(标签冗余) | 高(缩进简洁) | 中等(括号较多) | 高(SQL 原生) |
| 适用场景 | 企业级、复杂变更、需严格校验 | 快速原型、DevOps 流水线 | API 驱动、前端集成 | DBA 主导、已有 SQL 脚本迁移 |
4.2 四种格式的完整示例
以下分别展示四种格式下创建 person 表的等价 ChangeLog 片段:
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.20.xsd">
<changeSet id="1" author="dev">
<createTable tableName="person">
<column name="id" type="int" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="name" type="varchar(100)">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
</databaseChangeLog>
YAML 格式
databaseChangeLog:
- changeSet:
id: 1
author: dev
changes:
- createTable:
tableName: person
columns:
- column:
name: id
type: int
autoIncrement: true
constraints:
primaryKey: true
nullable: false
- column:
name: name
type: varchar(100)
constraints:
nullable: false
JSON 格式
{
"databaseChangeLog": [
{
"changeSet": {
"id": "1",
"author": "dev",
"changes": [
{
"createTable": {
"tableName": "person",
"columns": [
{
"column": {
"name": "id",
"type": "int",
"autoIncrement": true,
"constraints": {
"primaryKey": true,
"nullable": false
}
}
},
{
"column": {
"name": "name",
"type": "varchar(100)",
"constraints": {
"nullable": false
}
}
}
]
}
}
]
}
}
]
}
SQL 格式
-- liquibase formatted sql
-- changeset dev:1
CREATE TABLE person (
id INT AUTO_INCREMENT PRIMARY KEY NOT NULL,
name VARCHAR(100) NOT NULL
);
-- rollback DROP TABLE person;
-- liquibase formatted sql;每个变更集以 -- changeset author:id 开头;回滚语句以 -- rollback 开头。SQL 格式不支持 Liquibase 的 refactoring 标签(如 addColumn),必须手写 DDL。
4.3 include 与 includeAll:模块化组织策略
随着项目演进,单个 ChangeLog 文件会变得臃肿且难以维护。Liquibase 提供了 include 和 includeAll 两种指令,用于将多个 ChangeLog 文件组合成一个逻辑整体。
4.3.1 include:精确引用单个文件
include 用于显式引用一个子 ChangeLog 文件。支持相对路径、绝对路径以及 classpath 路径。常用属性如下:
| 属性 | 说明 | 示例 |
|---|---|---|
file |
子 ChangeLog 文件路径(必填) | file="changelogs/v1.0/create-tables.xml" |
relativeToChangelogFile |
路径是否相对于当前 ChangeLog 文件(默认 false) | relativeToChangelogFile="true" |
context |
按上下文过滤(如 dev、prod) | context="dev" |
labels |
按标签过滤 | labels="release-1.0" |
XML 示例:
<databaseChangeLog>
<include file="changelogs/v1.0/create-tables.xml"
relativeToChangelogFile="true"/>
<include file="changelogs/v1.0/seed-data.sql"
relativeToChangelogFile="true"
context="dev"/>
</databaseChangeLog>
YAML 示例:
databaseChangeLog:
- include:
file: changelogs/v1.0/create-tables.xml
relativeToChangelogFile: true
- include:
file: changelogs/v1.0/seed-data.sql
relativeToChangelogFile: true
context: dev
4.3.2 includeAll:批量扫描目录
includeAll 用于扫描指定目录下的所有 ChangeLog 文件,并按文件名排序后依次执行。适用于按版本或模块组织的目录结构。
| 属性 | 说明 | 示例 |
|---|---|---|
path |
扫描的目录路径(必填) | path="changelogs/v2.0/" |
relativeToChangelogFile |
路径是否相对于当前 ChangeLog 文件 | relativeToChangelogFile="true" |
filter |
文件名正则过滤器 | filter=".*\.xml" |
endsWithFilter |
按文件后缀过滤 | endsWithFilter=".sql" |
sortBy |
排序方式(file_name 或 natural) |
sortBy="natural" |
XML 示例:
<databaseChangeLog>
<includeAll path="changelogs/v2.0/"
relativeToChangelogFile="true"
endsWithFilter=".xml"
sortBy="natural"/>
</databaseChangeLog>
includeAll 时,建议按 sortBy="natural" 进行自然排序(如 001_create_table.xml、002_add_index.xml),避免因操作系统文件顺序不一致导致迁移顺序错乱。
4.4 模块化目录结构设计
结合 include 和 includeAll,推荐采用按版本 + 按模块的目录结构,实现清晰的变更管理:
changelogs/
├── master.xml # 根 ChangeLog,仅包含 include/includeAll
├── v1.0/
│ ├── 001_create_tables.xml
│ ├── 002_seed_data.sql
│ └── 003_add_indexes.xml
├── v1.1/
│ ├── 001_alter_users.xml
│ └── 002_add_orders.xml
└── v2.0/
├── 001_new_schema.xml
└── 002_migrate_data.sql
master.xml 示例:
<databaseChangeLog>
<includeAll path="v1.0/" relativeToChangelogFile="true"/>
<includeAll path="v1.1/" relativeToChangelogFile="true"/>
<includeAll path="v2.0/" relativeToChangelogFile="true"/>
</databaseChangeLog>
master.xml 中注释掉后续版本的 includeAll 即可。
4.5 ChangeLog 组织架构图
下图展示了从根 ChangeLog 到子文件的模块化组织关系,以及 include 与 includeAll 的引用路径:
4.6 四种格式的迁移执行流程对比
下图展示了 Liquibase 解析四种格式 ChangeLog 并执行迁移的流程差异:
4.7 常见问题与避坑指南
- 路径问题: 始终设置
relativeToChangelogFile="true",避免因工作目录不同导致文件找不到。 - 排序问题:
includeAll默认按文件名排序,建议使用sortBy="natural"并配合数字前缀(如001_、002_)确保顺序可控。 - 格式混用: 同一个
includeAll目录下可以混合 XML、YAML、JSON、SQL 文件,Liquibase 会根据文件后缀自动选择解析器。 - SQL 格式回滚: SQL 格式必须手动编写
-- rollback语句,否则 Liquibase 无法自动生成回滚 SQL。 - 上下文与标签: 使用
context和labels属性控制不同环境(dev/prod)的变更集执行,避免生产环境执行测试数据。
includeAll 目录中放置非 ChangeLog 文件(如 README.md、.gitkeep),否则 Liquibase 会尝试解析并报错。建议使用 endsWithFilter 或 filter 属性限定文件后缀。