如何引用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
相关产品推荐
相关产品推荐

