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

如何引用SpringBoot Jar同目录下而非包内的Liquibase变更日志

问题描述

我有一个基于Spring Boot 2.7.7的Liquibase数据库迁移项目,应用打包为.jar后,会和Liquibase变更日志一同打包进Docker镜像,运行时变更日志与.jar处于同一目录。

项目结构:

data-migration:
  src/
    main/
      java/
        com.example.demo/
          SpringBootApplication.java
          LiquibaseUpdate.java
      resources/
        application.properties
  build.gradle.kts

SpringBootApplication.java代码:

@SpringBootApplication
public class SpringBootApplication {

  public static void main(String[] args) {

    // 尝试将包含变更日志的额外目录添加到类路径
    System.setProperty(
        "java.class.path",
        System.getProperty("java.class.path")
            + ":/Users/rbyrne/git/data-migration/build/libs:."
    );

    new SpringApplicationBuilder(SpringBootApplication.class)
        .web(WebApplicationType.NONE)
        .run(args);
  }
}

LiquibaseUpdate.java代码:

@Configuration
@EnableConfigurationProperties({LiquibaseProperties.class})
public class LiquibaseUpdate {

  @Bean
  public SpringLiquibase liquibase(
      @Qualifier("taskExecutor") TaskExecutor taskExecutor,
      DataSource dataSource,
      LiquibaseProperties liquibaseProperties)
      throws Exception {
    SpringLiquibase liquibase = new SpringLiquibase();
    liquibase.setShouldRun(false); // 不对公共schema执行
    return liquibase;
  }

  @Bean("liquibaseMultiTenant")
  @DependsOn("liquibase") // 确保在SpringLiquibase Bean之后执行
  public MultiTenantSpringLiquibase liquibaseMultiTenant(
      DataSource dataSource, LiquibaseProperties liquibaseProperties) {
    MultiTenantSpringLiquibase liquibase = new MultiTenantSpringLiquibase();
    liquibase.setShouldRun(true);
    liquibase.setDataSource(dataSource);
    liquibase.setContexts(liquibaseProperties.getContexts());
    liquibase.setLabels(liquibaseProperties.getLabels());
    liquibase.setChangeLog("config/liquibase/changelog/root-changelog.xml");
    liquibase.setSchemas(Arrays.asList("tenant1", "tenant2"));
    return liquibase;
  }
}

运行时触发错误:

2023-03-23 15:06:13.596  WARN 96151 --- [           main] s.c.a.AnnotationConfigApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: 
Error creating bean with name 'liquibaseMultiTenant' defined in class path resource [com/example/demo/liquibase/LiquibaseCUpdate.class]: 
Invocation of init method failed; nested exception is liquibase.exception.ChangeLogParseException: 
The file config/liquibase/changelog/root-changelog.xml was not found in the configured search path:
    - Spring classpath
More locations can be added with the 'searchPath' parameter.

本地build/libs目录的文件结构:

work build/libs ‹main*› » pwd
/Users/rbyrne/git/data-migration/build/libs
work build/libs ‹main*› » ls
data-migration-0.1.0-SNAPSHOT-plain.jar   data-migration-0.1.0-SNAPSHOT.jar
data-migration-0.1.0-SNAPSHOT-sources.jar config
work build/libs ‹main*› » ls config/liquibase/changelog/
root-changelog.xml

已尝试在application.properties中配置searchPath但无效:

liquibase.search-path=./
liquibase.searchPath=./

需求:正确配置以让应用引用Jar同目录下的Liquibase变更日志。

解决方案

1. 删除无效的类路径修改代码

移除SpringBootApplication中手动修改java.class.path的代码,硬编码本地路径在Docker环境完全无效,且Spring Boot的自定义类加载器会忽略此类修改。修改后的启动类:

@SpringBootApplication
public class SpringBootApplication {

  public static void main(String[] args) {
    new SpringApplicationBuilder(SpringBootApplication.class)
        .web(WebApplicationType.NONE)
        .run(args);
  }
}

2. 配置Liquibase使用文件系统路径

Liquibase支持通过file:前缀指定文件系统路径,无需依赖类路径。修改LiquibaseUpdate中的配置:

@Bean("liquibaseMultiTenant")
@DependsOn("liquibase")
public MultiTenantSpringLiquibase liquibaseMultiTenant(
    DataSource dataSource, LiquibaseProperties liquibaseProperties) {
  MultiTenantSpringLiquibase liquibase = new MultiTenantSpringLiquibase();
  liquibase.setShouldRun(true);
  liquibase.setDataSource(dataSource);
  liquibase.setContexts(liquibaseProperties.getContexts());
  liquibase.setLabels(liquibaseProperties.getLabels());
  // 用file:前缀指定当前目录下的变更日志
  liquibase.setChangeLog("file:config/liquibase/changelog/root-changelog.xml");
  // 设置搜索路径为当前目录,确保子变更日志也能被加载
  liquibase.setSearchPath(".");
  liquibase.setSchemas(Arrays.asList("tenant1", "tenant2"));
  return liquibase;
}

3. 通过配置文件统一管理路径(可选)

若想通过application.properties统一配置,添加以下内容:

# 指定变更日志的文件系统路径
liquibase.change-log=file:config/liquibase/changelog/root-changelog.xml
# 设置搜索路径为当前工作目录
liquibase.search-path=.

然后在代码中复用配置属性,避免硬编码:

liquibase.setChangeLog(liquibaseProperties.getChangeLog());
liquibase.setSearchPath(liquibaseProperties.getSearchPath());

4. Docker镜像打包验证

确保Dockerfile将config目录与Jar放在同一工作目录,示例Dockerfile:

FROM openjdk:17-jdk-slim
WORKDIR /app
COPY build/libs/data-migration-0.1.0-SNAPSHOT.jar .
COPY build/libs/config ./config
CMD ["java", "-jar", "data-migration-0.1.0-SNAPSHOT.jar"]

核心原理

  • Liquibase默认从类路径查找资源,file:前缀强制从文件系统读取。
  • searchPath设为.表示当前工作目录,可确保所有子变更日志(若存在)通过相对路径被正确加载。
  • Spring Boot的Jar运行模式使用自定义类加载器,手动修改系统类路径的方式不生效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 11:03:08