问题描述
(编辑:我已经在问题中添加了一笔赏金。我找到了一个解决方法(在下面发布的答案),但我希望有人能解释为什么首先需要解决方法。)
( I've added a bounty to the question. I found a workaround (posted as an answer below), but I'm hoping somebody can explain why the workaround was necessary in the first place.)
我有一个Spring webapp,它在开发过程中连接到Derby数据库。这在第一次运行webapp时工作正常,但在后续运行中,它在启动时失败,Derby的另一个实例可能已经启动了数据库SQLException。
I have a Spring webapp that connects to a Derby database during development. This works fine the first time I run the webapp, but in subsequent runs it fails during startup with the "Another instance of Derby may have already booted the database" SQLException.
我知道这是因为当我关闭Tomcat时,与Derby的连接没有被关闭,即使我希望Spring自动处理它。所以我的问题是,如何正确断开与Derby的连接?不仅在手动停止Tomcat期间,而且在热部署新的.war文件期间?
I understand that this is because the connection to Derby isn't being closed when I shutdown Tomcat, even though I would expect Spring to handle that automatically. So my question is, how do I disconnect from Derby correctly? Not only during manually stopping Tomcat, but also during hot deploying of a new .war file?
我想避免使用Derby服务器,我也是使用注释而不是XML配置。这是我原来的PersistConfig类:
I'd like to avoid using a Derby server, and I'm also using annotations instead of XML configuration. Here was my original PersistConfig class:
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数据库的连接,但这似乎永远不会被解雇。
I tried adding a shutdown hook to the whole JVM using Runtime.addShutdownHook() where I manually disconnect from the Derby database, but that is seemingly never fired.
然后告诉我查看EmbeddedDatabaseConfigurer接口添加一个Spring关闭回调,我手动关闭数据库连接,这就是我想出的:
I was then told to look into the EmbeddedDatabaseConfigurer interface to add a Spring shutdown callback where I manually close the database connection, and this is what I came up with:
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()函数。我显然不知道我在做什么,所以任何指针都非常感激。
However, neither the configureConnectionProperties() function nor the shutdown() function seem to be called. I obviously don't know what I'm doing, so any pointers are greatly appreciated.
推荐答案
编辑:添加精度重新启动嵌入式Derby数据库和一个可能更简单的解决方案。
EDIT : add a precision of restarting embedded Derby database and a probably simpler solution.
我可以至少部分地重现问题,理解并修复它。但我不能说为什么 BoneCP
工作正常。我只是注意到,如果我在关闭tomcat并重新启动它之间等待足够多,那就可以了。我想BoneCP不会立即访问数据库,并等到第一次真正的连接。
I could reproduce at least partially the problem, understand it and fix it. But I cannot say why BoneCP
works fine. I simply noticed that if I waited enough between shutting down tomcat and restarting it again, it worked. I suppose that BoneCP do not access immediately the database, and waits enough until first real connection.
首先问题:当使用Derby作为嵌入式数据库时,数据库被引导在第一次连接时,它必须明确地关闭。如果不是,则不会删除 db.lock
文件,并且进一步的应用程序可能会在再次引导数据库时遇到问题。在tomcat中或者(默认情况下)在spring中没有任何东西可以自动关闭这样的数据库。
First the problem : when using Derby as an embedded database, the database is booted at first connection, but it has to be explicitely shutted down. If it is not, the db.lock
file is not deleted and further application may experience problems booting the database again. Nothing exists either in tomcat or (by default) in spring to automatically shutdown such a database.
接下来,为什么你尝试使用 EmbeddedDatabaseConfigurer
不起作用: EmbeddedDatabaseConfigurer
不是魔术标记,并且在类中继承它不足以让spring自动使用它。它只是一个必须由配置器实现的接口,允许 EmbeddedDatabaseFactory
使用它。
Next, why your attempt using an EmbeddedDatabaseConfigurer
didn't work : EmbeddedDatabaseConfigurer
is not a magic marker and inheriting it in a class is not enough to have spring automatically use it. It is simply an interface that must be implemented by a configurer to allow an EmbeddedDatabaseFactory
to use it.
最后固定。您不应该使用 SimpleDriverDataSource
来从嵌入式Derby数据库获取连接,而是使用 EmbeddedDatabaseFactory
。 Spring默认情况下知道Derby嵌入式数据库,你可以通过简单地设置类型来配置工厂......但是这只适用于内存数据库并且你有一个文件数据库!本来就太简单了......你必须向工厂注入一个配置器才能让一切正常。
Finally the fix. You should not use SimpleDriverDataSource
to get your connections from an embedded Derby database but an EmbeddedDatabaseFactory
. Spring by default knows Derby embedded database, and you can configure the factory by simply setting the type ... but that only works for in memory databases and you have a file database ! Would have been too simple ... You must inject the factory with a configurer to have all being ok.
现在代码(从你的第一个版本开始):
And now the code (starting from your first version) :
@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通常会被破坏。
This way, I can hot reload the war, and when tomcat is closed, the db.lock is normally destroyed.
编辑:
如果出现问题,Derby文档建议在关闭后添加以下命令以重新启动数据库: Class.forName(org.apache.derby.jdbc.EmbeddedDriver)。newInstance();
。它可能是 configureConnectionProperties
方法的最后一条指令。
In case of problems, Derby documentation advices to add the following command to restart a database after a shutdown : Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance();
. It could be the last instruction of the configureConnectionProperties
method.
但事实上,解决方案可能更简单。需要将真正添加到您的配置中的是正确关闭嵌入式驱动程序(最终重启)。所以一个简单的 PreDestroy
(和eventualy` @ PostConstruct)注释方法应该足够了:
But in fact, the solution could be even simpler. What really needs to be added to your config is a proper shutdown of the embedded driver (and eventually a restart). So a simple PreDestroy
(and eventualy `@PostConstruct) annotated method should be enough :
@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
到实际池。
The main interest of this variant is that you can choose your datasource, from a SimpleDriverDataSource
to a real pool.
这篇关于停止Tomcat不会删除Derby db.lck的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!