1. 引言

在数据库管理领域,确保模式变更的一致性和可追溯性是维护数据完整性和应用可靠性的关键实践。作为广泛采用的数据库模式变更管理工具,Liquibase能帮助我们在不同环境中无缝地版本控制和追踪数据库变更。

但随着应用复杂度增长,高效组织数据库对象变得至关重要。虽然PostgreSQL默认使用public模式,但最佳实践建议将应用特定实体隔离到专用模式中。

本教程将深入探讨在自定义模式中运行Liquibase,以及如何在Liquibase执行前解决创建PostgreSQL模式的挑战。 我们将通过一个连接PostgreSQL数据库的简单Spring Boot应用来演示实现过程。

2. Liquibase简介

Liquibase是管理数据库模式变更的解决方案。 它通过加速和简化数据库修改在各环境(从开发到生产)的修订和部署流程,帮助我们更安全地管理变更。

它使用SQL、XML、JSON和YAML格式的变更日志文件顺序记录数据库变更。数据库变更被称为变更集,变更集又包含变更类型——即应用于数据库的操作,如创建表或添加列。Liquibase在数据库中存储所有已应用的变更,当请求模式更新时,通过这些记录确定需要应用哪些新变更。

3. 应用搭建

首先需要搭建一个使用PostgreSQL作为数据库引擎、并用Liquibase生成表的简单Spring Boot应用。我们将选择Java 17和Spring Boot 3,因为后续需要用到第三方库。

3.1. Maven依赖

通过添加以下依赖配置应用:*spring-boot-starter-webspring-boot-started-data-jpaliquibase-corepostgresql*:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>3.2.3</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    <version>3.2.3</version>
</dependency>
<dependency>
    <groupId>org.liquibase</groupId>
    <artifactId>liquibase-core</artifactId>
    <version>4.26.0</version>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.7.2</version>
    <scope>runtime</scope>
</dependency>

3.2. PostgreSQL配置

application.properties文件中添加以下属性以使用PostgreSQL数据库:

spring.datasource.url=jdbc:postgresql://db.example.com/myapp_db
spring.datasource.username=dev_user
spring.datasource.password=secure_password

注意:需要将这些属性替换为与环境匹配的实际值。

3.3. 实体类

模拟一个用户管理应用,先定义简单的User实体:

@Entity
@Table(name = "users")
public class User {

    @Id
    private Long id;
    private String name;
    
    // 标准getter和setter
}

3.4. Liquibase变更日志

使用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-3.4.xsd">

    <changeSet id="1" author="unknown">
        <createTable tableName="users">
            <column name="id" type="bigint">
                <constraints primaryKey="true" nullable="false"/>
            </column>
            <column name="name" type="varchar(255)">
                <constraints nullable="false"/>
            </column>
        </createTable>
    </changeSet>
</databaseChangeLog>

application.properties中指定Liquibase使用的变更日志文件:

spring.liquibase.change-log=classpath:liquibase/changelog.xml

运行应用后,变更日志中的变更集将被应用,在public模式中创建三个表: 我们的users表以及Liquibase自动创建的databasechangelogdatabasechangeloglock表。

4. 在自定义模式中运行Liquibase

如前文所述,推荐在自定义模式中运行Liquibase。这能带来更好的数据库对象组织性和清晰度。自定义模式还提供隔离性和关注点分离,使应用的不同组件或模块能独立运行。

假设用户管理应用演变为模块化架构:现在需要处理用户权限等关联功能。使用不同模式分离这些概念将带来显著优势。

默认情况下Liquibase在public模式中运行。 要修改此行为,需在application.properties中覆盖default-schema属性:

spring.liquibase.default-schema=user_management

此方案使Liquibase完全在user_management模式中运行,databasechangelogdatabasechangeloglock表也会创建在自定义模式中。

另一种方式是修改变更集,直接在变更集中指定表所属模式:

<changeSet id="1" author="unknown">
    <createTable tableName="users" schemaName="user_management">
        <column name="id" type="bigint">
            <constraints primaryKey="true" nullable="false"/>
        </column>
        <column name="name" type="varchar(255)">
            <constraints nullable="false"/>
        </column>
    </createTable>
</changeSet>

此方案会导致databasechangelogdatabasechangeloglock表创建在public模式,而users表创建在user_management模式。

但两种方案存在共同缺陷:目标模式必须在Liquibase执行迁移前已存在。 如果目标模式不存在,应用将无法启动。后续章节将探讨几种在Liquibase执行前创建PostgreSQL模式的解决方案。

5. 在Liquibase执行前创建PostgreSQL模式

5.1. 自定义变更集

虽然Liquibase没有内置的模式创建变更类型,但可通过sql标签实现。为此创建新变更日志文件changelog-customChangeset.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-3.4.xsd">

    <changeSet id="0" author="unknown">
        <sql>
            CREATE SCHEMA IF NOT EXISTS user_management;
        </sql>
    </changeSet>
    <changeSet id="1" author="unknown">
        <createTable tableName="users" schemaName="user_management">
            <column name="id" type="bigint">
                <constraints primaryKey="true" nullable="false"/>
            </column>
            <column name="name" type="varchar(255)">
                <constraints nullable="false"/>
            </column>
        </createTable>
    </changeSet>
</databaseChangeLog>

此方案与设置Liquibase默认模式不兼容,因为变更集会在创建Liquibase专用表后执行。因此,*databasechangelog和*databasechangeloglock表仍会创建在public模式**。

5.2. Spring Bean方案

要覆盖Liquibase的默认模式,需确保模式已存在。可创建SchemaInitializer组件,在DataSource bean初始化后创建模式:

@Component
public class SchemaInitializer implements BeanPostProcessor {

    @Value("${spring.liquibase.default-schema}")
    private String schemaName;

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof DataSource) {
            DataSource dataSource = (DataSource) bean;
            try (Connection conn = dataSource.getConnection();
                 Statement statement = conn.createStatement()) {
                statement.execute(String.format("CREATE SCHEMA IF NOT EXISTS %s", schemaName));
            } catch (SQLException e) {
                throw new RuntimeException("模式创建失败", e);
            }
        }
        return bean;
    }
}

5.3. Pre-Liquibase方案

Pre-Liquibase模块允许在执行Liquibase变更集前运行SQL脚本。通常用于创建数据库模式或目录。

作为Spring Boot自动配置模块,只需添加依赖和要在Liquibase执行前运行的SQL脚本。此模块要求最低Java 17和Spring Boot 3.1。

添加preliquibase-spring-boot-starter依赖:

<dependency>
    <groupId>net.lbruun.springboot</groupId>
    <artifactId>preliquibase-spring-boot-starter</artifactId>
    <version>1.5.0</version>
</dependency>

Pre-Liquibase内置平台自动检测功能,会根据检测到的数据库平台选择运行的SQL脚本,因此需将脚本命名为postgresql.sql

CREATE SCHEMA IF NOT EXISTS user_management;

6. 总结

本文探讨了多种在自定义模式中运行Liquibase并在其执行前创建模式的方法。 通过利用自定义模式,我们能提升数据库对象的组织性、访问控制和隔离性,从而构建更具可扩展性和可维护性的应用。

完整源代码可在GitHub获取。


原始标题:Creating PostgreSQL Schema Before Liquibase Execution