停止Tomcat不会删除Derby db.lck

(编辑:我已经为问题添加了一笔赏金。我找到了一个解决方法(在下面发布的答案),但我希望有人可以解释为什么首先需要解决方法。)

我有一个Spring webapp,它在开发过程中连接到Derby数据库。 这在第一次运行webapp时工作正常,但在后续运行中,它在启动时失败,“Derby的另一个实例可能已经启动了数据库”SQLException。

我知道这是因为当我关闭Tomcat时,与Derby的连接没有被关闭,即使我希望Spring自动处理它。 所以我的问题是,如何正确断开与Derby的连接? 不仅在手动停止Tomcat期间,而且在热部署新的.war文件期间?

我想避免使用Derby服务器,我也使用注释而不是XML配置。 这是我原来的PersistConfig类:

package com.example.spring.config; import java.beans.PropertyVetoException; import java.sql.DriverManager; import java.sql.SQLException; import java.util.Properties; import javax.sql.DataSource; import org.apache.derby.jdbc.EmbeddedDataSource; import org.hibernate.SessionFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.SimpleDriverDataSource; import org.springframework.jdbc.datasource.embedded.ConnectionProperties; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseConfigurer; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.orm.hibernate4.HibernateExceptionTranslator; import org.springframework.orm.hibernate4.HibernateTransactionManager; import org.springframework.orm.hibernate4.LocalSessionFactoryBean; import org.springframework.orm.jpa.JpaVendorAdapter; import org.springframework.orm.jpa.vendor.Database; import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; import org.springframework.transaction.annotation.EnableTransactionManagement; @Configuration @ComponentScan({"com.example.spring.dao.jpa"}) @EnableTransactionManagement // <-- enable @Transactional annotations for spring @Component and stereotypes public class PersistConfig{ @Bean public HibernateExceptionTranslator exceptionTranslator() { return new HibernateExceptionTranslator(); } @Bean public LocalSessionFactoryBean localSessionFactoryBean(DataSource dataSource, JpaVendorAdapter vendorAdapter) { LocalSessionFactoryBean localSessionFactoryBean = new LocalSessionFactoryBean(); localSessionFactoryBean.setDataSource(dataSource); localSessionFactoryBean.setPackagesToScan("com.example.one", "com.example.two"); Properties properties = new Properties(); properties.putAll(vendorAdapter.getJpaPropertyMap()); localSessionFactoryBean.setHibernateProperties(properties); return localSessionFactoryBean; } @Bean public HibernateTransactionManager hibernateTransactionManager(SessionFactory sessionFactory) { HibernateTransactionManager hibernateTransactionManager = new HibernateTransactionManager(sessionFactory); return hibernateTransactionManager; } @Configuration public static class DevelopmentConfig{ @Bean public DataSource dataSource() throws SQLException, PropertyVetoException { DataSource dataSource = new SimpleDriverDataSource(new org.apache.derby.jdbc.EmbeddedDriver(), "jdbc:derby:C:\\Users\\Kevin\\Desktop\\DerbyDB", "", ""); System.out.println("RETURNING DATASOURCE"); return dataSource; } @Bean JpaVendorAdapter vendorAdapter() { HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); vendorAdapter.setDatabase(Database.DERBY); vendorAdapter.setDatabasePlatform("org.hibernate.dialect.DerbyDialect"); vendorAdapter.setGenerateDdl(true); vendorAdapter.setShowSql(true); vendorAdapter.getJpaPropertyMap().put("hibernate.hbm2ddl.auto", "update"); vendorAdapter.getJpaPropertyMap().put("hbm2ddl.auto", "update"); return vendorAdapter; } } } 

我尝试使用Runtime.addShutdownHook()向整个JVM添加一个关闭钩子,我手动断开与Derby数据库的连接,但这似乎永远不会被触发。

然后我被告知要查看EmbeddedDatabaseConfigurer接口以添加Spring关闭回调,我手动关闭数据库连接,这就是我想出的:

 package com.example.spring.config; import java.beans.PropertyVetoException; import java.sql.DriverManager; import java.sql.SQLException; import java.util.Properties; import javax.sql.DataSource; import org.apache.derby.jdbc.EmbeddedDataSource; import org.hibernate.SessionFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.SimpleDriverDataSource; import org.springframework.jdbc.datasource.embedded.ConnectionProperties; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseConfigurer; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.orm.hibernate4.HibernateExceptionTranslator; import org.springframework.orm.hibernate4.HibernateTransactionManager; import org.springframework.orm.hibernate4.LocalSessionFactoryBean; import org.springframework.orm.jpa.JpaVendorAdapter; import org.springframework.orm.jpa.vendor.Database; import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; import org.springframework.transaction.annotation.EnableTransactionManagement; @Configuration @ComponentScan({"com.example.spring.dao.jpa"}) @EnableTransactionManagement // <-- enable @Transactional annotations for spring @Component and stereotypes public class PersistConfig implements EmbeddedDatabaseConfigurer { @Bean public HibernateExceptionTranslator exceptionTranslator() { return new HibernateExceptionTranslator(); } @Bean public LocalSessionFactoryBean localSessionFactoryBean(DataSource dataSource, JpaVendorAdapter vendorAdapter) { LocalSessionFactoryBean localSessionFactoryBean = new LocalSessionFactoryBean(); localSessionFactoryBean.setDataSource(dataSource); localSessionFactoryBean.setPackagesToScan("com.example.one", "com.example.two"); Properties properties = new Properties(); properties.putAll(vendorAdapter.getJpaPropertyMap()); localSessionFactoryBean.setHibernateProperties(properties); return localSessionFactoryBean; } @Bean public HibernateTransactionManager hibernateTransactionManager(SessionFactory sessionFactory) { HibernateTransactionManager hibernateTransactionManager = new HibernateTransactionManager(sessionFactory); return hibernateTransactionManager; } @Configuration public static class DevelopmentConfig{ @Bean public DataSource dataSource() throws SQLException, PropertyVetoException { DataSource dataSource = new SimpleDriverDataSource(new org.apache.derby.jdbc.EmbeddedDriver(), "jdbc:derby:C:\\Users\\Kevin\\Desktop\\DerbyDB", "", ""); System.out.println("RETURNING DATASOURCE"); return dataSource; } @Bean JpaVendorAdapter vendorAdapter() { HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); vendorAdapter.setDatabase(Database.DERBY); vendorAdapter.setDatabasePlatform("org.hibernate.dialect.DerbyDialect"); vendorAdapter.setGenerateDdl(true); vendorAdapter.setShowSql(true); vendorAdapter.getJpaPropertyMap().put("hibernate.hbm2ddl.auto", "update"); vendorAdapter.getJpaPropertyMap().put("hbm2ddl.auto", "update"); return vendorAdapter; } } @Override public void configureConnectionProperties(ConnectionProperties properties, String databaseName) { System.out.println("CONFIGURE"); properties.setDriverClass(org.apache.derby.jdbc.EmbeddedDriver.class); properties.setUrl("jdbc:derby:C:\\Users\\Kevin\\Desktop\\DerbyDB"); } @Override public void shutdown(DataSource ds, String databaseName) { System.out.println("SHUTTING DOWN"); try { DriverManager.getConnection("jdbc:derby:C:\\Users\\Kevin\\Desktop\\DerbyDB;shutdown=true"); } catch (SQLException e) { e.printStackTrace(); } } } 

但是,似乎都没有调用configureConnectionProperties()函数和shutdown()函数。 我显然不知道我在做什么,所以任何指针都非常感激。

编辑:添加重新启动嵌入式Derby数据库的精度和一个可能更简单的解决方案。

我可以至少部分地重现问题,理解它并修复它。 但我不能说为什么BoneCP工作正常。 我只是注意到,如果我在关闭tomcat并重新启动它之间等待足够的时间,那就可以了。 我想BoneCP不会立即访问数据库,并等到第一次真正的连接。

首先是问题:当使用Derby作为嵌入式数据库时,数据库在第一次连接时启动,但必须明确地关闭它。 如果不是,则不会删除db.lock文件,并且进一步的应用程序可能会在再次引导数据库时遇到问题。 在tomcat中或者(默认情况下)在spring中没有任何东西可以自动关闭这样的数据库。

接下来,为什么使用EmbeddedDatabaseConfigurer的尝试不起作用: EmbeddedDatabaseConfigurer不是魔术标记,并且在类中inheritance它不足以让spring自动使用它。 它只是一个必须由配置器实现的接口,以允许EmbeddedDatabaseFactory使用它。

最后修复。 您不应该使用SimpleDriverDataSource从嵌入式Derby数据库获取连接,而是使用EmbeddedDatabaseFactory 。 Spring默认情况下知道Derby嵌入式数据库,你可以通过简单地设置类型来配置工厂……但是这只适用于内存数据库并且你有一个文件数据库! 本来就太简单了……你必须用一个配置器注入工厂才能让一切正常。

现在代码(从你的第一个版本开始):

 @Configuration public static class DevelopmentConfig{ EmbeddedDatabaseFactory dsFactory; public DevelopmentConfig() { EmbeddedDatabaseConfigurer configurer = new EmbeddedDatabaseConfigurer() { @Override public void configureConnectionProperties(ConnectionProperties properties, String databaseName) { System.out.println("CONFIGURE"); properties.setDriverClass(org.apache.derby.jdbc.EmbeddedDriver.class); properties.setUrl("jdbc:derby:C:\\Users\\Kevin\\Desktop\\DerbyDB"); } @Override public void shutdown(DataSource dataSource, String databaseName) { final String SHUTDOWN_CODE = "XJ015"; System.out.println("SHUTTING DOWN"); try { DriverManager.getConnection("jdbc:derby:;shutdown=true"); } catch (SQLException e) { // Derby 10.9.1.0 shutdown raises a SQLException with code "XJ015" if (!SHUTDOWN_CODE.equals(e.getSQLState())) { e.printStackTrace();; } } } }; dsFactory = new EmbeddedDatabaseFactory(); dsFactory.setDatabaseConfigurer(configurer); } @Bean public DataSource dataSource() throws SQLException, PropertyVetoException { System.out.println("RETURNING DATASOURCE"); return dsFactory.getDatabase(); } // remaining of code unchanged 

这样,我可以热重新加载战争,当tomcat关闭时,db.lock通常会被破坏。

编辑:如果出现问题,Derby文档建议在关闭后添加以下命令以重新启动数据库: Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance(); 。 它可能是configureConnectionProperties方法的最后一条指令。

但事实上,解决方案可能更简单。 真正需要添加到配置中的是正确关闭嵌入式驱动程序(最终重启)。 所以一个简单的PreDestroy (和eventualy` @ PostConstruct)注释方法就足够了:

 @Configuration public static class DevelopmentConfig{ @PreDestroy public void shutdown() { final String SHUTDOWN_CODE = "XJ015"; System.out.println("SHUTTING DOWN"); try { DriverManager.getConnection("jdbc:derby:;shutdown=true"); } catch (SQLException e) { // Derby 10.9.1.0 shutdown raises a SQLException with code "XJ015" if (!SHUTDOWN_CODE.equals(e.getSQLState())) { e.printStackTrace(); } } } /* if needed ... @PostConstruct public void init() throws InstantiationException, IllegalAccessException, ClassNotFoundException { Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance(); } */ @Bean public DataSource dataSource() throws SQLException, PropertyVetoException { DataSource dataSource = new SimpleDriverDataSource(new org.apache.derby.jdbc.EmbeddedDriver(), "jdbc:derby:C:\\Users\\Kevin\\Desktop\\DerbyDB", "", ""); System.out.println("RETURNING DATASOURCE"); return dataSource; } // remaining of code unchanged 

此变体的主要兴趣在于您可以选择数据源,从SimpleDriverDataSource到实际池。

我想出了一个问题的解决方案,虽然我不太明白为什么这个修复工作。 事实certificate,使用BoneCP配置DataSource可以解决问题,或者至少可以解决问题。

 public DataSource dataSource() throws SQLException, PropertyVetoException { BoneCPConfig config = new BoneCPConfig(); config.setUsername(""); config.setPassword(""); config.setJdbcUrl("jdbc:derby:C:\\Users\\Kevin\\Desktop\\DerbyDB"); BoneCPDataSource dataSource = new BoneCPDataSource(config); dataSource.setDriverClass("org.apache.derby.jdbc.EmbeddedDriver"); return dataSource; } 

奇怪的是,db.lck文件仍然永远不会被删除,但我根本没有看到任何错误,它似乎工作正常。

我添加这个作为答案,以防其他人有类似的问题,但我打开问题,以防有人可以向我解释为什么这解决了问题。

如果将Embedded Derby定义为Tomcat的DataSource(Resource),则可以使用LifeCycleListener执行Derby的关闭过程。

以下是Tomcat8的LifeCycleListener示例实现 ,以及设置的详细信息。