在开发企业级应用的时候,确保应用启动时数据的完整性和一致性绝对是件大事。这就好比给房子打地基,地基不稳,房子迟早会塌。今天,聊聊如何借助Spring Boot和Flyway的组合拳,优雅地搞定这个问题。
在处理大规模数据集时,通常可以使用Flyway来管理和维护这些数据。然而,当需要对Flyway已管理的数据进行更新或修改时,通过添加新的Flyway迁移脚本来实现这一目标可能会显得不够高效。在这种情况下,一种更为灵活的方法是在Spring Boot应用程序启动过程中执行特定的数据初始化或更新逻辑。
此外,对于那些只需要一次性执行的任务,类似于Flyway的迁移机制,但仅限于首次部署时运行且后续重启时不重复执行的需求,可以通过Flyway 的回调机制保证任务按照预期执行一次,又能避免不必要的重复操作。
Spring Boot 的 ApplicationRunner:启动时的“小助手”
首先说说Spring Boot的ApplicationRunner。这个接口就像是一个“启动管家”,它允许我们在应用启动完成后执行一些自定义任务。比如,我们可以用它来检查数据库是否正常、初始化一些默认数据,或者做一些必要的配置。
举个例子,假设我们有一个模板服务,需要在应用启动时检查数据库里是否存在某些默认模板。如果不存在,就自动创建它们。
@Slf4j
@Component
@Order(1)
public class InitDataRunner implements ApplicationRunner {
@Resource
private TemplateService templateService;
@Override
public void run(ApplicationArguments args) {
// 初始化模板
initTemplate("Template", "模板",
template -> template.setFlag(1),
"初始化模板 失败{}"
);
addTemplateIfNotExists("xxx", this::initTemplate, "新增模板", "新增模板失败{}");
}
/**
* 如果没有找到对应的模板,执行updater逻辑,创建新的模板并保存到数据库中。
* 如果有多个模板匹配,选择更新第一条模板。
*/
private void initTemplate(String templateCode, String logMessage, Consumer<Template> updater, String errorLog) {
try {
log.info("初始化模板:{}", logMessage);
List<Template> templates = templateService.lambdaQuery()
.eq(Template::getTemplateCode, templateCode)
// 根据需求调整查询条件
.list();
if (templates.isEmpty()) {
log.info("未找到符合条件的模板");
} else if (templates.size() == 1) {
updater.accept(templates.get(0));
templateService.updateById(templates.get(0));
} else {
log.warn("发现多条符合条件的模板,数量:{}", templates.size());
// 更新第一条数据
updater.accept(templates.get(0));
templateService.updateById(templates.get(0));
}
} catch (Exception e) {
log.error(errorLog, e);
}
}
/**
* 如果模板不存在,则新增模板
*
* @param templateCode 模板代码
* @param initMethod 初始化方法
* @param successLog 成功日志信息
* @param errorLog 错误日志信息
*/
private void addTemplateIfNotExists(String templateCode, Runnable initMethod, String successLog, String errorLog) {
try {
Template template = templateService.lambdaQuery()
.eq(Template::getTemplateCode, templateCode)
.one();
if (ObjectUtils.isEmpty(template)) {
initMethod.run();
log.info(successLog);
}
} catch (Exception e) {
log.error(errorLog, e);
}
}
/**
* 初始化模板
*/
private void initTemplate() {
try {
Template template = createTemplate(xxx);
templateService.save(template);
} catch (Exception e) {
log.error("保存失败: {}", e.getMessage());
}
}
这段代码的核心逻辑其实很简单:检查数据库中是否存在某个模板,如果不存在就创建它;如果存在多个模板,则更新第一条记录。这种逻辑在实际开发中很常见,比如在系统首次启动时,我们需要确保某些默认配置已经存在。
Flyway 的回调:让初始化任务只执行一次
接着,再来说说Flyway。Flyway是一个非常强大的数据库迁移工具,它可以帮我们管理数据库的版本和结构。不过,有时候我们不仅仅需要管理表结构,还需要在数据库中插入一些默认数据。比如,当我们部署一个新的应用版本时,可能需要在数据库中添加一些新功能所需的初始数据。
但是问题来了:如果我们直接用Flyway脚本来插入数据,每次应用重启时都会重复执行这些脚本,这显然不是我们想要的。那么,怎么才能确保某些初始化任务只在应用首次启动时执行一次呢?
答案就是Flyway的回调机制。通过实现Flyway的Callback接口,我们可以监听数据库迁移的事件,并在特定时刻执行自定义逻辑。比如,我们可以监听AFTER_MIGRATE事件,在所有迁移脚本执行完毕后,插入一些默认数据。
下面是一个简单的例子:
/**
* 只在部署之后执行一次
*/
@Slf4j
@Configuration
public class InitOutWorkProductData {
@Resource
private OAuthClientDetailsService oauthClientDetailsService;
@Bean
public Callback callback () {
return new Callback () {
// 判断是否初次启动
private boolean isFirst = true;
@Override
public boolean supports(Event event, Context context) {
return event == Event.BEFORE_MIGRATE || event == Event.AFTER_MIGRATE;
}
@Override
public boolean canHandleInTransaction(Event event, Context context) {
return false;
}
@Override
public void handle(Event event, Context context) {
if (!isFirst) {
return;
}
// 判断flyway记录表是否存在 Event.BEFORE_MIGRATE flyway的前置事件
if (event == Event.BEFORE_MIGRATE) {
try (Database database = DatabaseFactory.createDatabase(context.getConfiguration(), false)) {
Schema currentSchema = database.getMainConnection().getCurrentSchema();
Table table = currentSchema.getTable(context.getConfiguration().getTable());
isFirst = !table.exists();
}
}
// Event.BEFORE_MIGRATE flyway的后置事件
if (event == Event.AFTER_MIGRATE) {
isFirst = false;
// flyway完成之后执行一次
try {
//具体逻辑
}
}
}
};
}
在这个例子中,我们通过监听BEFORE_MIGRATE和AFTER_MIGRATE事件,确保初始化任务只在应用首次启动时执行一次。isFirst这个布尔变量就是用来标记是否是第一次启动的。
实际应用中的权衡
当然,选择哪种方式取决于具体的业务需求和团队的技术偏好。如果团队对Flyway脚本的管理已经非常成熟,可能更倾向于继续使用Flyway来处理所有数据相关的改动。但如果改动较为频繁或涉及动态逻辑,直接在Spring Boot中实现可能会更加高效。
我个人的经验是,在处理小规模或动态的数据更新时,直接在Spring Boot中实现通常更方便。而对于大规模的数据结构调整或初始化任务,Flyway依然是首选。此外,对于那些只需要执行一次的任务,结合Flyway的回调机制往往是最稳妥的选择,因为它与数据库版本控制紧密结合,能够更好地保证任务的执行顺序和一致性。
总之,无论是选择Flyway还是Spring Boot的方式,关键在于根据具体场景做出合理的选择。希望这篇文章能为你提供一些思路!