HibernateUtil 没有 return 连接到 c3p0 池

HibernateUtil does not return connection to c3p0 pool

在我们的 GWT web 应用程序中使用 Hibernate 出现数据库连接超时问题后,我们选择使用 c3p0 作为连接池提供程序。现在我有一个不同的问题:该应用似乎没有 return 连接到池 。 相反,它会在第一次访问数据库时停止。

为了调试问题,我遵循了 this question 中的建议,因为我的代码也挂在 awaitAvailable 中。所以我使用 checkoutTimeout 来防止客户端无限期地等待,并使用 unreturnedConnectionTimeoutdebugUnreturnedConnectionStackTraces 来获取没有 return 连接的代码部分的堆栈跟踪。令人惊讶的是,它们都是相同的代码(等待连接的代码和未 return 连接它的代码)并且都在我的 HibernateUtil class 中,它初始化会话工厂。

这是 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()。

但据我了解堆栈跟踪,它实际上在 class 加载中停止(而不是在对 getSessionFactory() 的调用中),我希望它是线程安全的。但是是java class加载和对应的静态块其实是线程安全的?

这是我的 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 需要多个连接来进行设置?为什么一个不够?

is java class loading and corresponding static blocks actually thread-safe?

是的。来自 Java Virtual Machine Specification:

Because the Java Virtual Machine is multithreaded, initialization of a class or interface requires careful synchronization, since some other thread may be trying to initialize the same class or interface at the same time. There is also the possibility that initialization of a class or interface may be requested recursively as part of the initialization of that class or interface. The implementation of the Java Virtual Machine is responsible for taking care of synchronization and recursive initialization by using the following procedure.

请注意,class 可能会被多个 classloader 加载,在这种情况下,您会看到调用了两次静态初始化程序。

But as far as I understand the stack trace it actually stalls in the class loading

有点。这就是堆栈跟踪中的 clinit (JVM spec 2.9)。这实际上发生在初始化期间,而不是加载期间。

<property name="hibernate.c3p0.timeout">300</property>

你确定这个值不是以毫秒为单位的吗? 300 非常短,可以轻松解释您看到的 debugUnreturnedConnectionStackTraces

maxPoolSize — 您通过 hibernate.c3p0.max_size 设置的 — 应该大于一。一个非常小的值会使您的应用程序容易冻结,如果一个操作需要超过 maxPoolSize 个连接并且无法完成它拥有的连接。请参阅主要问题下方的评论线程。