spring - SpringBoot应用程序销毁ApplicationContext后如何调用方法
问题描述
我有一个 SpringBoot 应用程序,它使用 Hikari jdbc 连接池来访问基于文件的 H2 数据库。
应用程序.yml
spring:
datasource:
hikari:
driver-class-name: org.h2.Driver
jdbc-url: jdbc:h2:${media.db.file};DB_CLOSE_ON_EXIT=FALSE
username:
password:
maximum-pool-size: 5
connection-test-query: "SELECT 1"
pool-name: media-pool
现在,我有一个要求,即在应用程序关闭后,我需要创建 h2 数据库文件的本地备份。
显然,这只会在数据库连接关闭后起作用,并且由于我有各种注入数据源的 bean,我假设数据库连接只会在这些 bean 被销毁后关闭(显然创建副本的类不能一颗春豆)。
所以我的问题是如何检测所有 bean 何时已被销毁并且应用程序上下文已被销毁。
显然,使用 @PreDestroy 注释方法将不起作用,因为在销毁 bean之前会调用此类方法。
我找到了各种注册应用程序生命周期事件的方法,但是,如果我BackupUtility.backupDatabase()
从其中任何一个调用我总是得到相同的异常:
java.io.IOException: The process cannot access the file because another process has locked a portion of the file
at java.base/java.io.FileInputStream.readBytes(Native Method)
at java.base/java.io.FileInputStream.read(FileInputStream.java:257)
at ch.bee.mediathek.business.utility.BackupUtility.backupDatabase(BackupUtility.java:63)
我认为这意味着数据库连接尚未关闭,因此我试图过早地创建备份。
我尝试了以下方法来通知我的应用程序关闭:
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(MediaSpringApplication.class, args);
context.registerShutdownHook();
log.info("Application started...");
context.addApplicationListener(MediaSpringApplication::onApplicationEvent);
}
private static void onApplicationEvent(final ApplicationEvent event) {
if (event instanceof ContextClosedEvent) {
log.info("===== ContextClosed (1) =====");
BackupUtility.backupDatabase();
}
}
@Bean
ServletListenerRegistrationBean<ServletContextListener> myServletListener() {
ServletListenerRegistrationBean<ServletContextListener> srb = new ServletListenerRegistrationBean<>();
srb.setListener(new MyServletContextListener());
return srb;
}
public class MyServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
log.info("===== ContextDestroyed (2) =====");
BackupUtility.backupDatabase();
}
}
@EventListener({ContextClosedEvent.class})
public void contextClosed() {
log.info("===== ContextClosed (3) =====");
BackupUtility.backupDatabase();
}
查看日志输出,我看到以下内容:
[2021-01-06 19:38:12.023] [Thread-8] [INFO ] c.b.m.a.MediaSpringApplication::contextClosed(144) - ===== ContextClosed (3) =====
[2021-01-06 19:38:12.030] [Thread-8] [ERROR] c.b.m.b.u.BackupUtility::backupDatabase(67) - Error while copying file contents
java.io.IOException: Der Prozess kann nicht auf die Datei zugreifen, da ein anderer Prozess einen Teil der Datei gesperrt hat
at java.base/java.io.FileInputStream.readBytes(Native Method)
at java.base/java.io.FileInputStream.read(FileInputStream.java:257)
at ch.bee.mediathek.business.utility.BackupUtility.backupDatabase(BackupUtility.java:63)
at ch.bee.mediathek.app.MediaSpringApplication.contextClosed(MediaSpringApplication.java:145)
[2021-01-06 19:38:12.030] [Thread-8] [INFO ] c.b.m.a.MediaSpringApplication::onApplicationEvent(56) - ===== ContextClosed (1) =====
[2021-01-06 19:38:12.031] [Thread-8] [ERROR] c.b.m.b.u.BackupUtility::backupDatabase(67) - Error while copying file contents
java.io.IOException: Der Prozess kann nicht auf die Datei zugreifen, da ein anderer Prozess einen Teil der Datei gesperrt hat
at java.base/java.io.FileInputStream.readBytes(Native Method)
at java.base/java.io.FileInputStream.read(FileInputStream.java:257)
at ch.bee.mediathek.business.utility.BackupUtility.backupDatabase(BackupUtility.java:63)
at ch.bee.mediathek.app.MediaSpringApplication.onApplicationEvent(MediaSpringApplication.java:57)
[2021-01-06 19:38:12.138] [Thread-8] [INFO ] c.b.m.a.MediaSpringApplication::contextDestroyed(137) - ===== ContextDestroyed (2) =====
[2021-01-06 19:38:12.140] [Thread-8] [ERROR] c.b.m.b.u.BackupUtility::backupDatabase(67) - Error while copying file contents
java.io.IOException: Der Prozess kann nicht auf die Datei zugreifen, da ein anderer Prozess einen Teil der Datei gesperrt hat
at java.base/java.io.FileInputStream.readBytes(Native Method)
at java.base/java.io.FileInputStream.read(FileInputStream.java:257)
at ch.bee.mediathek.business.utility.BackupUtility.backupDatabase(BackupUtility.java:63)
at ch.bee.mediathek.app.MediaSpringApplication$MyServletContextListener.contextDestroyed(MediaSpringApplication.java:138)
[2021-01-06 19:38:12.393] [Thread-8] [WARN ] o.s.b.f.s.DisposableBeanAdapter::destroy(267) - Invocation of destroy method failed on bean with name 'inMemoryDatabaseShutdownExecutor': org.h2.jdbc.JdbcSQLNonTransientConnectionException: Database is already closed (to disable automatic closing at VM shutdown, add ";DB_CLOSE_ON_EXIT=FALSE" to the db URL) [90121-200]
[2021-01-06 19:38:12.395] [Thread-8] [INFO ] c.z.h.HikariDataSource::close(350) - media-pool - Shutdown initiated...
[2021-01-06 19:38:12.398] [Thread-8] [INFO ] c.z.h.HikariDataSource::close(352) - media-pool - Shutdown completed.
查看日志的最后四行,我发现当我在最后一次回调(由 ServletContextListener 触发的回调)之后尝试备份数据库文件时,该文件显然仍处于打开状态。但是,在下一行我看到 DisposableBeanAdapter 尝试做一些失败的事情,因为数据库已经关闭。接下来的两行显示 HikariDataSource 类甚至更晚才关闭。
所以,似乎我需要在 HikariDataSource 关闭媒体池后找到一种方法来启动我的备份。我本来希望当这些 bean 被销毁时会发生这种情况,并且我的上下文销毁回调只会在发生之后调用,但它似乎不是那样工作的。
我试图从spring.datasource.hikari.jdbc-url
我的application.yml文件中删除 DB_CLOSE_ON_EXIT=FALSE 但仍然无法创建备份,但此外还在多次关闭结束时添加了以下异常(可能每个数据池成员一次):
[2021-01-06 20:04:30.466] [Thread-9] [WARN ] c.z.h.p.PoolBase::isConnectionAlive(184) - media-pool - Failed to validate connection conn1: url=jdbc:h2:/workspaces/media-spring/database/Media-local user= (Database is already closed (to disable automatic closing at VM shutdown, add ";DB_CLOSE_ON_EXIT=FALSE" to the db URL) [90121-200]). Possibly consider using a shorter maxLifetime value.
难道我做错了什么?还是有其他方法可以在以后通知我还没有找到?
解决方案
推荐阅读
- laravel - 在响应中添加特定的相关字段
- scala - 拥有多个 Scala SimpleSwingApplication 实例
- python - Twitch Bot URL 恶意软件/病毒检测 PYTHON
- tesseract - 如何在 PA 上更新 Tesseract?
- sql - 如何使 ORACLE SQL 上的值为 0 为空?
- javascript - Google App Scripts - 如何根据另一个单元格清除多个单元格的内容?
- flutter - 实现此主屏幕的最佳方法是什么?
- macos - How to pass value from apple script to shell script in Automator?
- c - Difference between function with returned type pointer and function pointer
- flyway - Flyway 社区版是否支持 Oracle 19.7?