如何让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实现步骤:
- 在
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>
- 编写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); } } } }
- 修改
db.changelog-master.yaml,引用自动生成的排序Changelog:
databaseChangeLog: - include: file: db/changelog/sorted-changelog.yaml
每次构建时,插件会自动生成按文件名排序的Changelog,Liquibase将按顺序执行所有迁移脚本。
方案二:自定义Liquibase资源访问器
通过自定义ResourceAccessor合并两个目录的资源并按文件名排序,注入到Spring的Liquibase Bean中实现排序执行。
实现步骤:
- 编写自定义
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; } }
- 在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; } }
- 修改
db.changelog-master.yaml为单个includeAll:
databaseChangeLog: - includeAll: path: db/ filter: *.sql
此方案通过代码干预资源加载顺序,实现跨目录的统一时间排序执行。
内容的提问来源于stack exchange,提问作者big_OS
相关产品推荐
相关产品推荐

