Skip to content

Liquibase 使用说明

sz-admin 使用 Liquibase 管理数据库表结构和初始化数据。v2.0.0 起,数据库脚本随模块拆分:启动服务只保留聚合入口,具体表结构和初始化数据由 sz-module-adminsz-module-auditsz-module-generator 等业务模块维护。

为什么使用 Liquibase

  • 版本可追溯:每个 changeSet 都有唯一 id,Liquibase 通过 DATABASECHANGELOG 表记录执行历史,不会重复执行。
  • 模块化维护:服务入口聚合模块级 changelog,模块内部再按 frameworkdemogenerator 等能力分层。
  • 多数据库兼容:需要同时兼容 MySQL 和 PostgreSQL 的模块,可通过 <property dbms="...">changeSet dbms="..." 维护差异。
  • 环境幂等:通过 <preConditions onFail="MARK_RAN"> 避免已有对象重复创建。
  • 可选执行:通过 spring.liquibase.enabled 控制是否在启动时自动执行,生产环境可关闭后人工审核脚本。

当前目录结构

v2.0.0 当前实际入口如下:

text
sz-service/sz-service-admin/
└── src/main/resources/
    └── db/changelog/
        └── changelog-master.xml                 # 服务级聚合入口

sz-module/sz-module-admin/
└── src/main/resources/
    └── db/changelog/
        ├── module-admin-changelog.xml           # admin 模块入口
        ├── framework/
        │   ├── changelog-master.xml             # framework 分层入口
        │   └── 2.0.0/
        │       ├── 001_framework_init.xml       # framework 2.0.0 清单
        │       └── tables/
        │           ├── 000_public_type_def.xml
        │           ├── 001_sys_client.xml
        │           ├── 002_sys_config.xml
        │           ├── ...
        │           └── 025_sys_dict_source.xml
        └── demo/
            ├── changelog-master.xml             # demo 分层入口
            └── 2.0.0/
                ├── 001_demo_init.xml            # demo 2.0.0 清单
                └── tables/
                    ├── 001_sys_user_dept.xml
                    ├── ...
                    └── 015_teacher_statistics.xml

sz-module/sz-module-generator/
└── src/main/resources/
    └── db/changelog/
        ├── module-generator-changelog.xml       # generator 模块入口
        └── generator/
            ├── changelog-master.xml             # generator 分层入口
            └── 2.0.0/
                ├── 001_generator_init.xml       # generator 2.0.0 清单
                └── tables/
                    ├── 001_generator_table.xml
                    └── 002_generator_table_column.xml

入口关系

服务级入口位于 sz-service-admin

xml
<!-- sz-service/sz-service-admin/src/main/resources/db/changelog/changelog-master.xml -->
<databaseChangeLog ...>
    <include file="db/changelog/module-admin-changelog.xml"/>
    <include file="db/changelog/module-generator-changelog.xml"/>
</databaseChangeLog>

module-admin-changelog.xml 聚合 admin 模块内的 framework 与 demo:

xml
<databaseChangeLog ...>
    <include file="framework/2.0.0/001_framework_init.xml" relativeToChangelogFile="true"/>
    <include file="demo/2.0.0/001_demo_init.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

module-generator-changelog.xml 聚合代码生成器模块:

xml
<databaseChangeLog ...>
    <include file="generator/2.0.0/001_generator_init.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

NOTE

framework/changelog-master.xmldemo/changelog-master.xmlgenerator/changelog-master.xml 仍保留为分层入口,便于独立维护或后续按模块拆分引用;当前服务主链路直接通过模块级 changelog 引入具体版本清单。

分层说明

层级所在模块用途是否必须
framework/sz-module-admin框架核心表结构与必要初始化数据,例如客户端、配置、字典、菜单、角色、用户等必须
demo/sz-module-admin演示/测试数据,例如示例用户、示例部门、教师统计数据等可选,生产环境可按需注释
generator/sz-module-generator代码生成器表结构,例如 generator_tablegenerator_table_column可选,不使用生成器时可注释模块入口

可选模块关闭

生产环境通常至少保留 framework。如果不需要 demo 或 generator,可在服务级或模块级入口注释对应 include。

xml
<!-- 服务级入口:不需要代码生成器时可注释 -->
<include file="db/changelog/module-admin-changelog.xml"/>
<!-- <include file="db/changelog/module-generator-changelog.xml"/> -->
xml
<!-- admin 模块入口:生产环境不需要演示数据时可注释 -->
<include file="framework/2.0.0/001_framework_init.xml" relativeToChangelogFile="true"/>
<!-- <include file="demo/2.0.0/001_demo_init.xml" relativeToChangelogFile="true"/> -->

WARNING

已经在某个数据库执行过的 changeSet 不建议修改 idauthor 或文件路径,否则 Liquibase 可能将其识别为新的变更集。需要变更结构时,应追加新的 changeSet

执行控制

Liquibase 配置在数据库配置文件中,例如 mysql.ymlpostgresql.yml

yaml
spring:
  liquibase:
    change-log: classpath:db/changelog/changelog-master.xml
    enabled: true
    database-change-log-lock-table: databasechangeloglock
    database-change-log-table: databasechangelog
配置项说明
change-log服务级入口,当前为 classpath:db/changelog/changelog-master.xml
enabled: true应用启动时自动检测并执行未执行的 changeSet
enabled: false不自动执行,适合生产环境人工审核后手动执行
database-change-log-tableLiquibase 执行记录表
database-change-log-lock-tableLiquibase 执行锁表

类型变量

需要同时兼容 MySQL 和 PostgreSQL 的 changelog,可在 XML 头部声明类型变量,Liquibase 会按当前数据库自动选择:

xml
<property name="bool.type"     value="CHAR(1)"    dbms="mysql"/>
<property name="bool.type"     value="VARCHAR(1)" dbms="postgresql"/>
<property name="datetime.type" value="DATETIME"   dbms="mysql"/>
<property name="datetime.type" value="TIMESTAMP"  dbms="postgresql"/>

使用示例:

xml
<column name="del_flag"    type="${bool.type}"     defaultValue="F" remarks="删除标志"/>
<column name="create_time" type="${datetime.type}" remarks="创建时间"/>

这类字段可以用一个 changeSet 同时覆盖 MySQL 和 PostgreSQL。若项目已固定目标数据库,也可以直接写目标库类型。

changeSet 命名规范

推荐格式:

text
{scope}-{operation}-{table-name}-{yyyyMMddHHmm}
部分说明示例
scope数据层级或模块,如 frameworkdemogeneratorframework
operationstructinitdataalterstruct
table-name表名,用连字符分隔sys-client
时间精确到分钟,同分钟多个 changeSet 可追加序号202605100838

示例:

text
framework-struct-sys-client-202605100838
framework-init-sys-client-202605100838
generator-struct-generator-table-202605100838
demo-data-teacher-statistics-202605100838

preConditions 用法

所有结构类 changeSet 建议添加 <preConditions onFail="MARK_RAN">,避免已有数据库重复执行时报错。

建表前检查表是否存在:

xml
<changeSet id="framework-struct-sys-dept-202605100838" author="sz">
    <preConditions onFail="MARK_RAN">
        <not><tableExists tableName="sys_dept"/></not>
    </preConditions>
    <createTable tableName="sys_dept" remarks="部门表">
        ...
    </createTable>
</changeSet>

插入数据前检查记录是否存在:

xml
<changeSet id="framework-init-sys-client-202605100838" author="sz">
    <preConditions onFail="MARK_RAN">
        <sqlCheck expectedResult="0">SELECT COUNT(*) FROM sys_client</sqlCheck>
    </preConditions>
    <insert tableName="sys_client">
        ...
    </insert>
</changeSet>
onFail行为
MARK_RAN条件不满足时跳过该 changeSet,并在 DATABASECHANGELOG 中标记为已执行
HALT条件不满足时抛出异常,停止后续执行
CONTINUE条件不满足时跳过该 changeSet,但不标记为已执行

双 dbms 写法

当某个字段类型在两种数据库中存在无法通过变量统一的差异时,例如 dept_scope 或大文本,是否双写取决于兼容目标:

  • 固定只支持 MySQL 的业务项目:直接使用 MySQL 类型即可,不需要 PostgreSQL 版 changeSet。
  • 固定只支持 PostgreSQL 的业务项目:直接使用 PostgreSQL 类型即可,不需要 MySQL 版 changeSet。
  • 需要同时兼容 MySQL + PostgreSQL 的官方模块、插件模块或可复用模块:使用 dbms="mysql" / dbms="postgresql" 分别维护两个 changeSet。

双库兼容示例:

xml
<!-- MySQL 版:dept_scope=JSON,大文本=MEDIUMTEXT -->
<changeSet id="demo-struct-your-table-202606010900" author="sz" dbms="mysql">
    <preConditions onFail="MARK_RAN">
        <not><tableExists tableName="your_table"/></not>
    </preConditions>
    <createTable tableName="your_table" remarks="你的表">
        <column name="id"          type="BIGINT"      remarks="ID"><constraints primaryKey="true"/></column>
        <column name="name"        type="VARCHAR(64)" remarks="名称"/>
        <column name="del_flag"    type="${bool.type}" defaultValue="F" remarks="删除标志"/>
        <column name="create_time" type="${datetime.type}" remarks="创建时间"/>
        <column name="dept_scope"  type="JSON"        remarks="部门范围(数据权限)"/>
        <column name="content"     type="MEDIUMTEXT"  remarks="正文内容"/>
    </createTable>
</changeSet>

<!-- PostgreSQL 版:dept_scope=BIGINT[],大文本=TEXT -->
<changeSet id="demo-struct-your-table-pg-202606010900" author="sz" dbms="postgresql">
    <preConditions onFail="MARK_RAN">
        <not><tableExists tableName="your_table"/></not>
    </preConditions>
    <createTable tableName="your_table" remarks="你的表">
        <column name="id"          type="BIGINT"      remarks="ID"><constraints primaryKey="true"/></column>
        <column name="name"        type="VARCHAR(64)" remarks="名称"/>
        <column name="del_flag"    type="${bool.type}" defaultValue="F" remarks="删除标志"/>
        <column name="create_time" type="${datetime.type}" remarks="创建时间"/>
        <column name="dept_scope"  type="BIGINT[]"    remarks="部门范围(数据权限)"/>
        <column name="content"     type="TEXT"        remarks="正文内容"/>
    </createTable>
</changeSet>

判断原则:

  • 固定目标数据库为 MySQL 或 PostgreSQL:按目标库写单个 changeSet 即可。
  • 字段类型可以用 ${bool.type}${datetime.type} 表达:单个 changeSet 即可兼容双库。
  • 同时兼容双库,且字段是 dept_scope 或大文本:需要按 dbms 双写。
  • VARCHARINTBIGINTDECIMAL 等通用类型:通常单个 changeSet 即可。

初始化数据写法

初始化数据优先使用 <insert> 标签。数值类型字段用 valueNumeric,字符串字段用 value,日期字段用 valueDate

xml
<changeSet id="framework-init-sys-client-202605100838" author="sz">
    <preConditions onFail="MARK_RAN">
        <sqlCheck expectedResult="0">SELECT COUNT(*) FROM sys_client WHERE client_id = '195da9fcce574852b850068771cde034'</sqlCheck>
    </preConditions>
    <insert tableName="sys_client">
        <column name="client_id"      value="195da9fcce574852b850068771cde034"/>
        <column name="client_key"     value="sz-admin"/>
        <column name="active_timeout" valueNumeric="86400"/>
        <column name="timeout"        valueNumeric="604800"/>
        <column name="del_flag"       value="F"/>
        <column name="create_id"      valueNumeric="1"/>
        <column name="create_time"    valueDate="2024-01-22 13:43:51"/>
        <column name="is_lock"        value="T"/>
    </insert>
</changeSet>

NOTE

初始化数据本身通常不需要 dbms 区分。只有表结构或 SQL 函数依赖特定数据库能力时,才需要按数据库拆分。

已执行记录查询

Liquibase 执行历史保存在 DATABASECHANGELOG 表中:

sql
SELECT id, author, filename, dateexecuted, exectype
FROM DATABASECHANGELOG
ORDER BY dateexecuted;

SELECT id, author, exectype
FROM DATABASECHANGELOG
WHERE exectype = 'MARK_RAN';
exectype含义
EXECUTED正常执行完成
MARK_RAN因 preConditions 不满足而被标记跳过
RERAN设置 runAlways="true" 的 changeSet 被重新执行
FAILED执行失败