spring - 共享的 C3P0 JNDI 数据源在 Jetty 的 servlet 取消部署期间关闭,其他 servlet 不再可以访问

标签 spring servlets jetty jndi c3p0

几个 servlet 在我的 Jetty 容器中运行。 所有这些 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 的问题,它无法再访问数据库。发生同样的问题,例如与数据库支持的存储库交互时。

我使用:

  • jetty 9.1.0.v20131115
  • C3P0 0.9.1.1 ...(我也试过0.9.5-pre6,同样的症状)
  • MYSQL J/Connector 5.1.27

JNDI DataSource 在我的 Jetty XML 配置中是这样设置的:

<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&amp;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&amp;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 DataSourceSpring 在 servlet 的 处关闭Spring 容器关闭时间(即发出 rm -f ${jetty.base}/webapps/c.war 时)。

解释
我的 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 具有以下相关语义:

As a convenience to the user, the container will attempt to infer a destroy method against object returned from the @Bean method. [...] This 'destroy method inference' is currently limited to detecting only public, no-arg methods named close. The method may be declared at any level of the inheritance hierarchy, and will be detected regardless of the return type of the @Bean method, i.e. detection occurs reflectively against the bean instance itself at creation time.

To disable destroy method inference for a particular @Bean, specify an empty string as the value, e.g. @Bean(destroyMethod="").

事实证明 com.mchange.v2.c3p0.ComboPooledDataSource 确实有 close() 方法(在其父类(super class)型 com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource).

因此,当我的 servlet 取消部署时,在 Spring 容器关闭时间,Spring 将调用 ComboPooledDataSource.close()。恕我直言,在语义上,在这种特定情况下,Spring 调用此方法是错误的。

解决我的问题的方法是用 @Bean(destroyMethod="") 注释我的 DataSource 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 都可以正常地取消部署、重新部署和部署。

关于spring - 共享的 C3P0 JNDI 数据源在 Jetty 的 servlet 取消部署期间关闭,其他 servlet 不再可以访问,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20749379/

相关文章:

java - Spring /thymeleaf : How to fill a list in form and append another line to the form?

http - 处理用户的多个同时 HttpSession

php - GWT 前端(托管模式)和 PHP 后端(apache)同时在本地主机上?

单事务中的 Spring 集成和 JDBC

spring - @SpringBootTest : Context not loading and Spock unable to @Autowire 的问题

java - 将 servlet 移动到 jar 文件以用作库

java - 空上下文字符串警告

java - jetty Hello World 示例不编译

java - Jetty 没有接受我的 Spring 申请

java - Spring MVC - 使用输出流和 HttpServletResponse 下载 PDF