我的Jetty容器中运行着几个servlet。所有这些servlet都使用一个通过JNDI公开的DataSource
。此DataSource
是C3P0 ComboPooledDataSource
。
目前,我取消部署任何这些servlet,以某种方式ComboPooledDataSource
被“关闭”。从这一刻起,已经部署的servlet和任何其他已部署的servlet都将无法再访问DataSource
。因此,所有需要DataSource
的servlet在下一次访问该DataSource
时停止工作。
这是一个注释的堆栈跟踪:
## Undeploying a servlet named "c.war" by issuing "rm -f ${jetty.base}/webapps/c.war"
## Thread "Scanner-0" recognizes that something has changed in the webapps directory,
## therefore "Scanner-0" shuts down components inside the c.war servlet:
2013-12-23 17:19:11,977 container [Scanner-0] INFO c - Destroying Spring FrameworkServlet 'appServlet'
2013-12-23 17:19:11,977 container [Scanner-0] INFO o.s.w.c.s.XmlWebApplicationContext - Closing WebApplicationContext for namespace 'appServlet-servlet': startup date [Mon Dec 23 17:18:01 CET 2013]; parent: Root WebApplicationContext
2013-12-23 17:19:11,977 container [Scanner-0] INFO o.s.b.f.s.DefaultListableBeanFactory - Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@6def78d2: defining beans []; parent: org.springframework.beans.factory.support.DefaultListableBeanFactory@477ed07f
2013-12-23 17:19:11,980 container [Scanner-0] INFO c - Closing Spring root WebApplicationContext
2013-12-23 17:19:11,980 container [Scanner-0] INFO o.s.w.c.s.AnnotationConfigWebApplicationContext - Closing Root WebApplicationContext: startup date [Mon Dec 23 17:17:37 CET 2013]; root of context hierarchy
2013-12-23 17:19:11,998 container [Scanner-0] INFO o.s.c.s.DefaultLifecycleProcessor - Stopping beans in phase 2147483647
2013-12-23 17:19:12,003 container [Scanner-0] INFO org.quartz.core.QuartzScheduler - Scheduler my_scheduler_$_NON_CLUSTERED paused.
2013-12-23 17:19:12,006 container [Scanner-0] INFO o.s.b.f.s.DefaultListableBeanFactory - Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@477ed07f: defining beans [long, list, of, my, spring, beans, shortened, for, brevity]; root of factory hierarchy
2013-12-23 17:19:12,007 container [Scanner-0] INFO org.apache.tiles.access.TilesAccess - Removing TilesContext for context: org.springframework.web.servlet.view.tiles2.SpringTilesApplicationContextFactory$SpringWildcardServletTilesApplicationContext
2013-12-23 17:19:12,014 container [Scanner-0] INFO o.s.s.quartz.SchedulerFactoryBean - Shutting down Quartz Scheduler
2013-12-23 17:19:12,014 container [Scanner-0] INFO org.quartz.core.QuartzScheduler - Scheduler my_scheduler_$_NON_CLUSTERED shutting down.
2013-12-23 17:19:12,014 container [Scanner-0] INFO org.quartz.core.QuartzScheduler - Scheduler my_scheduler_$_NON_CLUSTERED paused.
2013-12-23 17:19:12,056 container [Scanner-0] INFO org.quartz.core.QuartzScheduler - Scheduler my_scheduler_$_NON_CLUSTERED shutdown complete.
2013-12-23 17:19:12,155 container [Scanner-0] INFO o.e.j.server.handler.ContextHandler - Stopped o.e.j.w.WebAppContext@676e4e1c{/c,file:/tmp/jetty-0.0.0.0-8080-c.war-_c-any-8486076679973394405.dir/webapp/,UNAVAILABLE}{/c.war}
## Thread "Scanner-0" has finished undeploying c.war
## A few seconds later, thread "my_scheduler_QuartzSchedulerThread" from unrelated
## servlet "b.war" tries to do stuff with the JNDI-obtained DataSource, but fails:
2013-12-23 17:19:19,688 container [my_scheduler_QuartzSchedulerThread] ERROR org.quartz.core.ErrorLogger - An error occurred while scanning for the next triggers to fire.
org.quartz.JobPersistenceException: Failed to obtain DB connection from data source 'springNonTxDataSource.my_scheduler': java.sql.SQLException: com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, contextClassLoaderSource -> caller, dataSourceName -> ai8gp08z8xy71x5vsabw|11f7562b, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, identityToken -> ai8gp08z8xy71x5vsabw|11f7562b, idleConnectionTestPeriod -> 0, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=UTF-8, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 28000, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 40, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 20, numHelperThreads -> 3, preferredTestQuery -> null, privilegeSpawnedThreads -> false, properties -> {user=******, password=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ] has been closed() -- you can no longer use it.
at org.quartz.impl.jdbcjobstore.JobStoreCMT.getNonManagedTXConnection(JobStoreCMT.java:168) ~[quartz-2.2.1.jar:na]
at org.quartz.impl.jdbcjobstore.JobStoreSupport.executeInNonManagedTXLock(JobStoreSupport.java:3784) ~[quartz-2.2.1.jar:na]
at org.quartz.impl.jdbcjobstore.JobStoreSupport.acquireNextTriggers(JobStoreSupport.java:2756) ~[quartz-2.2.1.jar:na]
at org.quartz.core.QuartzSchedulerThread.run(QuartzSchedulerThread.java:272) ~[quartz-2.2.1.jar:na]
Caused by: java.sql.SQLException: com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, contextClassLoaderSource -> caller, dataSourceName -> ai8gp08z8xy71x5vsabw|11f7562b, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, identityToken -> ai8gp08z8xy71x5vsabw|11f7562b, idleConnectionTestPeriod -> 0, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=UTF-8, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 28000, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 40, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 20, numHelperThreads -> 3, preferredTestQuery -> null, privilegeSpawnedThreads -> false, properties -> {user=******, password=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ] has been closed() -- you can no longer use it.
at com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource.assertCpds(AbstractPoolBackedDataSource.java:507) ~[c3p0-0.9.5-pre6.jar:0.9.5-pre6]
at com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource.getPoolManager(AbstractPoolBackedDataSource.java:519) ~[c3p0-0.9.5-pre6.jar:0.9.5-pre6]
at com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource.getConnection(AbstractPoolBackedDataSource.java:140) ~[c3p0-0.9.5-pre6.jar:0.9.5-pre6]
at org.springframework.scheduling.quartz.LocalDataSourceJobStore$2.getConnection(LocalDataSourceJobStore.java:129) ~[spring-context-support-3.2.4.RELEASE.jar:3.2.4.RELEASE]
at org.quartz.utils.DBConnectionManager.getConnection(DBConnectionManager.java:108) ~[quartz-2.2.1.jar:na]
at org.quartz.impl.jdbcjobstore.JobStoreCMT.getNonManagedTXConnection(JobStoreCMT.java:165) ~[quartz-2.2.1.jar:na]
... 3 common frames omitted
所以这里的相关信息是:
An error occurred while scanning for the next triggers to fire.
org.quartz.JobPersistenceException:
Failed to obtain DB connection from data source 'springNonTxDataSource.my_scheduler':
java.sql.SQLException: com.mchange.v2.c3p0.ComboPooledDataSource [...] has been closed() -- you can no longer use it.
上面显示了Quartz Scheduler的问题,它不再可以访问数据库。发生同样的问题与数据库支持的存储库进行交互时。
我用:
码头
9.1.0.v20131115
C3P0
0.9.1.1
...(我也尝试过0.9.5-pre6
,相同的症状)MYSQL J /连接器
5.1.27
在我的Jetty XML配置中,JNDI
DataSource
的设置如下:<Configure id="Server" class="org.eclipse.jetty.server.Server">
<New id="jdbc-mydb" class="org.eclipse.jetty.plus.jndi.Resource">
<Arg></Arg>
<Arg>jdbc/mydb</Arg>
<Arg>
<New class="com.mchange.v2.c3p0.ComboPooledDataSource">
<Set name="DriverClass">com.mysql.jdbc.Driver</Set>
<Set name="JdbcUrl">jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=UTF-8</Set>
<Set name="User">user</Set>
<Set name="Password">pass</Set>
<Set name="MaxPoolSize">40</Set>
<Set name="MinPoolSize">20</Set>
<Set name="MaxIdleTime">28000</Set>
</New>
</Arg>
</New>
</Configure>
当我使用普通的MySQL驱动程序而不是C3P0时,则我的servlet可以毫无问题地进行部署,取消部署和重新部署:
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<New id="jdbc-mydb" class="org.eclipse.jetty.plus.jndi.Resource">
<Arg></Arg>
<Arg>jdbc/mydb</Arg>
<Arg>
<New class="com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource">
<Set name="Url">jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=UTF-8</Set>
<Set name="User">user</Set>
<Set name="Password">pass</Set>
</New>
</Arg>
</New>
</Configure>
如何在不“关闭” C3P0
DataSource
的情况下取消部署servlet? 最佳答案
就我而言,该症状原来不是由C3P0还是由Jetty引起的:JNDI共享的C3P0 DataSource
在Servlet的Spring容器关闭时(即发出rm -f ${jetty.base}/webapps/c.war
时)由Spring关闭。
说明
我的servlet是基于Spring的。我利用Spring的JavaConfig。在基于Spring的servlet中,我使用@Configuration
这样的类:
@Configuration
public class MainConfig {
//...
@Bean
DataSource dataSource() {
DataSource myds = null;
JndiTemplate jndi = new JndiTemplate();
try {
myds = (DataSource) jndi.lookup("java:comp/env/jdbc/mydb");
} catch (NamingException e) {
logger.error("NamingException for java:comp/env/jdbc/mydb", e);
}
return myds;
}
}
Spring的
@Bean
具有以下相关语义:为了方便用户,容器将尝试针对从
@Bean
方法返回的对象推断一个destroy方法。 [...]这种“破坏方法的推论”目前仅限于仅检测名为close
的公共无参数方法。该方法可以在继承层次结构的任何级别上声明,并且无论@Bean
方法的返回类型如何,都将对其进行检测,即,检测是在创建时针对bean实例本身进行反射的。要禁用特定
@Bean
的destroy方法推断,请指定一个空字符串作为值,例如@Bean(destroyMethod="")
。事实证明
com.mchange.v2.c3p0.ComboPooledDataSource
确实具有close()
方法(在其超类型com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource
中)。因此,当取消部署我的servlet时,在Spring容器关闭时,Spring将调用
ComboPooledDataSource.close()
。恕我直言,从语义上讲,在这种特殊情况下,Spring调用该方法是错误的。解决我的问题的方法是用
DataSource
注释@Bean(destroyMethod="")
bean:@Configuration
public class MainConfig {
//...
@Bean(destroyMethod="")
DataSource dataSource() {
DataSource myds = null;
JndiTemplate jndi = new JndiTemplate();
try {
myds = (DataSource) jndi.lookup("java:comp/env/jdbc/mydb");
} catch (NamingException e) {
logger.error("NamingException for java:comp/env/jdbc/mydb", e);
}
return myds;
}
}
现在,我所有的servlet都可以轻松地取消部署,重新部署和部署了。