You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Liquibase跨目录按统一时间顺序执行数据库迁移?

问题:Liquibase跨目录迁移脚本按统一时间顺序执行

我需要将部分特定changeset文件迁移至另一个目录,未来两个目录都会新增迁移脚本。src/main/resources/db下的文件结构如下:

.
├── changelog
│   └── db.changelog-master.yaml
├── migration
│   └── ... sql changesets ...
└── migration-specific
    └── ... sql changesets ...

db.changelog-master.yaml的配置内容:

databaseChangeLog:
  - includeAll:
      path: db/migration/
  - includeAll:
      path: db/migration-specific/

当前问题:文件名采用YYYYMMDD_X_的日期加序号命名,本应保证执行顺序正确,但Liquibase会先执行完一个目录下的所有脚本,再执行另一个目录的脚本。

例如现有文件:

.
├── migration
│   ├── 20240501_1_...
│   └── 20240701_1_...
└── migration-specific
    └── 20240601_1_...

当前执行顺序:

20240501_1_...
20240701_1_...
20240601_1_...

期望执行顺序:

20240501_1_...
20240601_1_...
20240701_1_...

使用版本:

  • liquibase-core: 4.17.0
  • spring boot: 2.7.18

解决方案

可以实现跨目录按统一时间顺序执行,以下是两种可行方案:

方案一:自动生成排序后的统一Changelog(推荐)

通过构建工具自动扫描两个目录的迁移文件,按文件名排序后生成包含所有include语句的中间Changelog,再让主Changelog引用该文件。

Maven实现步骤:

  1. 在pom.xml中添加exec-maven-plugin,用于执行生成脚本:
<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>exec-maven-plugin</artifactId>
    <version>3.1.0</version>
    <executions>
        <execution>
            <id>generate-sorted-changelog</id>
            <phase>process-resources</phase>
            <goals>
                <goal>java</goal>
            </goals>
            <configuration>
                <mainClass>com.yourpackage.GenerateSortedChangelog</mainClass>
                <arguments>
                    <argument>${project.basedir}/src/main/resources/db/migration</argument>
                    <argument>${project.basedir}/src/main/resources/db/migration-specific</argument>
                    <argument>${project.build.outputDirectory}/db/changelog/sorted-changelog.yaml</argument>
                </arguments>
            </configuration>
        </execution>
    </executions>
</plugin>
  1. 编写Java工具类GenerateSortedChangelog,完成文件扫描、排序和Changelog生成:
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class GenerateSortedChangelog {
    public static void main(String[] args) throws IOException {
        String migrationDir = args[0];
        String specificDir = args[1];
        String outputPath = args[2];

        List<File> changeSetFiles = new ArrayList<>();
        collectSqlFiles(new File(migrationDir), changeSetFiles);
        collectSqlFiles(new File(specificDir), changeSetFiles);

        // 按文件名排序
        changeSetFiles.sort(Comparator.comparing(File::getName));

        // 生成YAML格式的Changelog内容
        StringBuilder yaml = new StringBuilder("databaseChangeLog:\n");
        for (File file : changeSetFiles) {
            String relativePath = "db/" + file.getParentFile().getName() + "/" + file.getName();
            yaml.append("  - include:\n")
                .append("      file: ").append(relativePath).append("\n");
        }

        // 写入输出文件
        File outputFile = new File(outputPath);
        outputFile.getParentFile().mkdirs();
        try (FileWriter writer = new FileWriter(outputFile)) {
            writer.write(yaml.toString());
        }
    }

    private static void collectSqlFiles(File dir, List<File> files) {
        if (dir.isDirectory()) {
            for (File file : dir.listFiles((d, name) -> name.endsWith(".sql"))) {
                files.add(file);
            }
        }
    }
}
  1. 修改db.changelog-master.yaml,引用自动生成的排序Changelog:
databaseChangeLog:
  - include:
      file: db/changelog/sorted-changelog.yaml

每次构建时,插件会自动生成按文件名排序的Changelog,Liquibase将按顺序执行所有迁移脚本。

方案二:自定义Liquibase资源访问器

通过自定义ResourceAccessor合并两个目录的资源并按文件名排序,注入到Spring的Liquibase Bean中实现排序执行。

实现步骤:

  1. 编写自定义SortedResourceAccessor:
import liquibase.resource.CompositeResourceAccessor;
import liquibase.resource.SearchPathResourceAccessor;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;

public class SortedResourceAccessor extends CompositeResourceAccessor {

    public SortedResourceAccessor(String... paths) throws IOException {
        super(new SearchPathResourceAccessor(paths));
    }

    @Override
    public Collection<String> list(String relativeTo, String path, boolean includeFiles, boolean includeDirectories, boolean recursive) throws IOException {
        Collection<String> resources = super.list(relativeTo, path, includeFiles, includeDirectories, recursive);
        // 过滤SQL文件并按文件名排序
        List<String> sortedSqlFiles = new ArrayList<>();
        for (String resource : resources) {
            if (resource.endsWith(".sql")) {
                sortedSqlFiles.add(resource);
            }
        }
        sortedSqlFiles.sort(Comparator.comparing(s -> s.substring(s.lastIndexOf('/') + 1)));
        return sortedSqlFiles;
    }
}
  1. 在Spring配置类中自定义Liquibase Bean:
import liquibase.integration.spring.SpringLiquibase;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
import java.io.IOException;

@Configuration
public class LiquibaseConfig {

    @Bean
    public SpringLiquibase liquibase(DataSource dataSource) throws IOException {
        SpringLiquibase liquibase = new SpringLiquibase();
        liquibase.setDataSource(dataSource);
        liquibase.setChangeLog("classpath:db/changelog/db.changelog-master.yaml");
        // 使用自定义资源访问器指定两个迁移目录
        liquibase.setResourceAccessor(new SortedResourceAccessor("classpath:db/migration/", "classpath:db/migration-specific/"));
        return liquibase;
    }
}
  1. 修改db.changelog-master.yaml为单个includeAll:
databaseChangeLog:
  - includeAll:
      path: db/
      filter: *.sql

此方案通过代码干预资源加载顺序,实现跨目录的统一时间排序执行。


内容的提问来源于stack exchange,提问作者big_OS

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 23:05:55