【问题标题】:Stopping Tomcat Doesn't Delete Derby db.lck停止 Tomcat 不会删除 Derby db.lck
【发布时间】:2014-08-20 21:29:41
【问题描述】:

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

我有一个在开发过程中连接到 Derby 数据库的 Spring webapp。这在我第一次运行 web 应用程序时工作正常,但在随后的运行中它在启动过程中失败,并出现“另一个 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() 函数似乎都没有被调用。我显然不知道我在做什么,所以任何指针都非常感谢。

【问题讨论】:

  • 只有在重新部署应用程序时才会出现问题,还是在以下情况下也会出现问题:1/停止,取消部署,稍等(> 30秒),重新部署和2/停止tomcat,重新启动雄猫?
  • @SergeBallesta 无论我做什么都会发生:我尝试停止 tomcat,从 Tomcat 的 webapp 目录中删除 .war 和目录,然后将新的 .war 文件复制到 webapp 目录中,然后再次启动Tomcat。我还尝试通过复制新的 .war 文件来“热部署”。两者都生成 SQLException。

标签: java spring tomcat spring-mvc derby


【解决方案1】:

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

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

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

接下来,为什么您尝试使用 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

这样我就可以热重装war了,当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 到真实池。

【讨论】:

  • 这似乎解决了我的问题,但现在当我通过在 Tomcat 运行时将新版本的 .war 文件复制到 webapp 目录进行热部署时,它会更新我的 webapp 文件夹和 db .lck 文件被删除然后重新创建,但是我尝试加载的任何页面都会挂起而不会出现错误。当我尝试停止 Tomcat 时,它似乎停止了,但是再次启动它的选项也被禁用了。我必须重新启动计算机才能再次运行所有内容。知道是什么原因造成的吗?
  • 我真的无法重现您的上一个问题。我找到了一个更简单的解决方案(更新后),并且 Derby 文档建议添加 Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance(); 以重新启动嵌入式驱动程序。但是我注意到您在配置中混合了 JPA 和本机休眠……我想您已经进行了干净的构建?
  • 感谢您的调查。我真的不知道我在做什么,所以当你说我正在混合 JPA 和本机休眠时,我什至不知道这意味着什么。我想了解更多信息,但我知道这不是 StackOverflow 的真正问题类型。有人告诉我,热部署是不可靠的,无论如何也不用担心,所以虽然我很想解决挂起问题(或者至少了解它发生的原因),但我可能会继续前进。
【解决方案2】:

我找到了解决这个问题的办法,虽然我不太明白为什么这个解决办法有效。事实证明,使用 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 文件仍然从未被删除,但我根本没有看到任何错误,而且它似乎工作正常。

我将此添加为答案,以防其他人遇到类似问题,但我将保留此问题,以防有人可以向我解释为什么这可以解决问题。

【讨论】:

    【解决方案3】:

    如果您将 Embedded Derby 定义为 Tomcat 的数据源(资源),则可以使用执行 Derby 关闭过程的 LifeCycleListener。

    这里是a sample implementation of LifeCycleListener for Tomcat8detail of setup.

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-08-16
      • 2014-10-25
      • 2014-06-19
      • 1970-01-01
      • 2021-09-09
      • 1970-01-01
      • 1970-01-01
      • 2012-09-04
      相关资源
      最近更新 更多