停止Tomcat不会删除Derby db.lck


问题内容

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

我有一个在开发过程中连接到Derby数据库的Spring Webapp。第一次运行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
shutdown回调,在其中我手动关闭数据库连接,这是我想到的:

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(默认情况下)都不存在自动关闭此类数据库的功能。

接下来,为什么尝试使用an
EmbeddedDatabaseConfigurer无效:EmbeddedDatabaseConfigurer不是魔术标记,并且在类中继承它不足以让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(最终是@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到实际池。