Spring Boot中如何确保Liquibase YAML脚本优先于SQL脚本执行?
解决方案
方法1:监听Liquibase执行完成事件
利用Liquibase发布的LiquibaseEndEvent事件,在Schema变更完全完成后手动执行数据脚本,这是最可靠的顺序控制方式。
步骤:
- 禁用默认数据源初始化:在
application.properties中关闭Spring Boot自动执行spring.datasource.data指定的脚本,避免冲突:
spring.datasource.initialization-mode=never
- 编写事件监听器:创建Spring组件,监听Liquibase执行结束事件,通过Spring内置的
ScriptUtils工具执行数据脚本:
@Component public class PostLiquibaseDataLoader { private final JdbcTemplate jdbcTemplate; private final ResourceLoader resourceLoader; public PostLiquibaseDataLoader(JdbcTemplate jdbcTemplate, ResourceLoader resourceLoader) { this.jdbcTemplate = jdbcTemplate; this.resourceLoader = resourceLoader; } @EventListener public void runDataScriptsAfterLiquibase(LiquibaseEndEvent event) { // 按执行顺序定义数据脚本路径 String[] dataScripts = { "classpath:data/user_init.sql", "classpath:data/product_init.sql" }; for (String scriptPath : dataScripts) { try { Resource scriptResource = resourceLoader.getResource(scriptPath); // 自动处理SQL分隔符、连接管理和异常 ScriptUtils.executeSqlScript( jdbcTemplate.getDataSource().getConnection(), scriptResource ); } catch (Exception e) { throw new RuntimeException("Failed to execute data script: " + scriptPath, e); } } } }
方法2:通过Bean依赖控制执行顺序
使用@DependsOn注解强制负责数据脚本执行的DataSourceInitializer Bean,在Liquibase Bean初始化完成后再启动。
步骤:
- 禁用默认初始化:
spring.datasource.initialization-mode=never
- 自定义DataSourceInitializer Bean:
@Configuration public class DataLoaderConfig { @Bean @DependsOn("liquibase") // 明确依赖Liquibase Bean,确保Schema变更先完成 public DataSourceInitializer dataSourceInitializer(DataSource dataSource) { DataSourceInitializer initializer = new DataSourceInitializer(); initializer.setDataSource(dataSource); ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); populator.addScripts( new ClassPathResource("data/user_init.sql"), new ClassPathResource("data/product_init.sql") ); // 脚本执行失败时终止(可根据需求改为true) populator.setContinueOnError(false); initializer.setDatabasePopulator(populator); // 设置初始化模式:ALWAYS(每次启动执行)、EMBEDDED(仅嵌入式数据库)、NEVER initializer.setMode(DataSourceInitializationMode.ALWAYS); return initializer; } }
最佳实践
- 环境区分执行:通过Spring Profile控制数据脚本仅在开发/测试环境自动执行,生产环境手动触发或通过状态表判断:
@Profile({"dev", "test"}) @Component public class PostLiquibaseDataLoader { ... } - 防止重复执行:生产环境中创建
data_load_status表,记录数据加载状态,执行脚本前先检查状态,避免重复插入数据:@EventListener public void runDataScriptsAfterLiquibase(LiquibaseEndEvent event) { Boolean isLoaded = jdbcTemplate.queryForObject( "SELECT EXISTS(SELECT 1 FROM data_load_status WHERE status = 'COMPLETED')", Boolean.class ); if (Boolean.TRUE.equals(isLoaded)) { return; } // 执行脚本... jdbcTemplate.execute("INSERT INTO data_load_status (status, load_time) VALUES ('COMPLETED', NOW())"); } - 脚本维护原则:保持数据脚本与最终Schema结构一致,避免依赖中间Schema版本,降低长期维护成本。
内容的提问来源于stack exchange,提问作者EmreAkkoc
相关产品推荐
相关产品推荐

