【发布时间】:2015-03-03 18:11:50
【问题描述】:
在我们的 GWT webapp 中使用 Hibernate 时出现数据库连接超时的问题后,我们选择使用 c3p0 作为连接池提供程序。现在我有一个不同的问题:该应用似乎没有将连接返回到池。 相反,它会在第一次数据库访问时停止。
为了调试问题,我遵循了this question 中的建议,因为我的代码也挂在了 awaitAvailable 中。所以我使用checkoutTimeout 来防止客户端无限期地等待,并使用unreturnedConnectionTimeout 和debugUnreturnedConnectionStackTraces 来获取不返回连接的部分代码的堆栈跟踪。令人惊讶的是,它们都是相同的代码(等待连接和不返回连接的代码),并且都在我的 HibernateUtil 类中,它初始化了会话工厂。
这是debugUnreturnedConnectionStackTraces保存的堆栈跟踪:
java.lang.Exception: DEBUG STACK TRACE: Overdue resource check-out stack trace.
at com.mchange.v2.resourcepool.BasicResourcePool.checkoutResource(BasicResourcePool.java:555)
at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool.checkoutAndMarkConnectionInUse(C3P0PooledConnectionPool.java:755)
at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool.checkoutPooledConnection(C3P0PooledConnectionPool.java:682)
at com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource.getConnection(AbstractPoolBackedDataSource.java:140)
at org.hibernate.c3p0.internal.C3P0ConnectionProvider.getConnection(C3P0ConnectionProvider.java:90)
at org.jadira.usertype.spi.shared.AbstractUserTypeHibernateIntegrator.use42Api(AbstractUserTypeHibernateIntegrator.java:80)
at org.jadira.usertype.spi.shared.AbstractUserTypeHibernateIntegrator.integrate(AbstractUserTypeHibernateIntegrator.java:61)
at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:312)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1859)
at my.package.domain.hibernate.HibernateUtil.<clinit>(HibernateUtil.java:17)
[... snip ...] // user code calling HibernateUtil.getSessionFactory()
awaitAvailable() 超时的类似堆栈跟踪:
com.mchange.v2.resourcepool.TimeoutException: A client timed out while waiting to acquire a resource from com.mchange.v2.resourcepool.BasicResourcePool@ae6163 -- timeout at awaitAvailable()
at com.mchange.v2.resourcepool.BasicResourcePool.awaitAvailable(BasicResourcePool.java:1416)
at com.mchange.v2.resourcepool.BasicResourcePool.prelimCheckoutResource(BasicResourcePool.java:606)
at com.mchange.v2.resourcepool.BasicResourcePool.checkoutResource(BasicResourcePool.java:526)
at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool.checkoutAndMarkConnectionInUse(C3P0PooledConnectionPool.java:755)
at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool.checkoutPooledConnection(C3P0PooledConnectionPool.java:682)
at com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource.getConnection(AbstractPoolBackedDataSource.java:140)
at org.hibernate.c3p0.internal.C3P0ConnectionProvider.getConnection(C3P0ConnectionProvider.java:90)
at org.jadira.usertype.spi.shared.AbstractUserTypeHibernateIntegrator.use42Api(AbstractUserTypeHibernateIntegrator.java:80)
at org.jadira.usertype.spi.shared.AbstractUserTypeHibernateIntegrator.integrate(AbstractUserTypeHibernateIntegrator.java:61)
at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:312)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1859)
at my.package.domain.hibernate.HibernateUtil.<clinit>(HibernateUtil.java:17)
[... snip ...] // same client code calling HibernateUtil.getSessionFactory()
对服务器的一个请求就足以导致这个问题,所以没有多个请求。但是调用getSessionFactory 的代码看起来像这样:
public class MyClass
{
private SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
// [...] snip
}
因此可能是 MyClass 的对象在应用程序的不同部分并行实例化,这可能导致对 getSessionFactory() 的多次调用。
但据我了解堆栈跟踪它实际上在类加载中停止(而不是在对getSessionFactory() 的调用中),我希望它是线程安全的。但是java类加载和相应的静态块实际上是线程安全的吗?
这是我的 HibernateUtil 代码:
public class HibernateUtil
{
private static SessionFactory sessionFactory;
static
{
Configuration configuration = new Configuration().configure();
StandardServiceRegistryBuilder builder = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties());
sessionFactory = configuration.buildSessionFactory(builder.build());
}
public static SessionFactory getSessionFactory()
{
return sessionFactory;
}
}
应该这样工作吗?有什么需要改进的地方吗?
为了完整起见,这里是我的 hibernate.cfg.xml
<hibernate-configuration>
<session-factory>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">
<property name="show_sql">true</property>
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- Connection pool configuration -->
<property name="connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>
<property name="hibernate.c3p0.min_size">1</property>
<property name="hibernate.c3p0.max_size">1</property>
<property name="hibernate.c3p0.timeout">300</property>
<property name="hibernate.c3p0.max_statements">50</property>
<property name="hibernate.c3p0.idle_test_period">90</property>
<property name="hibernate.c3p0.checkoutTimeout">10000</property><!-- milliseconds -->
<property name="hibernate.c3p0.debugUnreturnedConnectionStackTraces">true</property><!-- do not use in production -->
<property name="hibernate.c3p0.unreturnedConnectionTimeout">60</property>
<!-- Configure automatic session management: https://developer.jboss.org/wiki/Sessionsandtransactions#jive_content_id_Transaction_demarcation_with_plain_JDBC -->
<property name="hibernate.transaction.factory_class">org.hibernate.transaction.JDBCTransactionFactory</property>
<property name="hibernate.current_session_context_class">thread</property>
<!-- Configure automatic mapping for Joda Time classes like DateTime and
Instant -->
<property name="jadira.usertype.autoRegisterUserTypes">true</property>
<property name="jadira.usertype.javaZone">UTC</property>
<property name="jadira.usertype.databaseZone">UTC</property>
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- lots of mapped classes -->
</session-factory>
</hibernate-configuration>
编辑:按照@Steve Waldmann 的建议,我增加了 maxPoolSize,结果如下:
* 2 Connections lead to the same problem
* 4 Connections lead to the same problem
* __8 Connections worked__
然后我再次尝试使用较小的 maxPoolSize,现在 4 个连接也可以正常工作。但是 3 个连接仍然失败。我认为问题是由于我所做的架构更新造成的。显然休眠需要更多的连接来进行架构升级,但至少在我的情况下至少需要 3 个连接来进行初始设置。
所以我想剩下的唯一问题是,为什么 Hibernate 需要多个连接来进行设置?为什么一个还不够?
【问题讨论】:
-
首先要设置一个合理的
maxPoolSize(这里为hibernate.c3p0.max_size)。 c3p0 是一个连接池,而不是一个连接单例。如果任何操作需要同时使用多个连接,则 1 的池将耗尽,嗯,很容易,并且可能会冻结。. 设置合理的maxPoolSize可能不足以解决您的问题,在这种情况下,我们到时候再说。但是在你解决这个问题之前,花太多时间在这上面是没有意义的。 “hibernate.c3p0.”是怎么回事?也许那曾经是debugUnreturnedConnectionStackTraces? -
抱歉,
debugUnreturnedConnectionStackTraces在复制代码时不知何故迷路了。是的,你是对的,缺少的位是debugUnreturnedConnectionStackTraces,现在在问题中修复它。 -
关于
maxPoolSize:我的印象是在开发过程中使用小池在早期发现连接泄漏是一种很好的做法。我可能天真地假设我正在做的所有事情都应该只使用一个连接就可以了,因为我没有并行执行任何数据库操作并且一次只使用一个休眠会话。对于一个请求,也不应该有任何可能导致并行连接的多线程。 -
@Steve Waldmann 我增加了
maxPoolSize,这实际上解决了问题。但是,如上所述,我认为一个连接应该足以满足一个请求。但显然 Hibernate 需要多个连接来进行初始设置,尤其是当它对数据库执行模式更新时。你知道为什么会这样吗?无论如何,如果您添加“增加 maxPoolSize,这是正确的做法”作为答案,我会接受它。 ;-) -
池子小也可以,太小就自找麻烦。正如您所遇到的那样,如果有任何单个操作一次期望超过
maxPoolSizeConnections,那么这是保证冻结。我不知道 hiberante 的SessionFactory和架构初始化的内部结构,但您的经验确实建议在某些情况下在内部使用多个连接。
标签: java multithreading hibernate connection-pooling c3p0