SpringMVC,c3p0,hibernate,JPA应用程序泄漏连接导致Too Many Connections错误

时间:2015-04-04 19:36:25

标签: java mysql spring hibernate c3p0

我已经用Google搜索并搜索了stackoverflow,以便解决我的问题超过4个小时,阅读并尝试了解正在发生的事情,但我没有遇到与我的问题相关的解决方案,所以如果这听起来很道歉就像重复一样,我会尽力解释究竟发生了什么,这样我就可以深入了解c3p0的内部工作原理。

我有一个在Tomcat上运行的SpringMVC Web应用程序,Hibernate,JPA和C3P0作为我的池资源。在页面重新加载时,应用程序会立即调用数据库以获取随机图像并将其显示在新页面上。应用程序运行正常,大约30页左右的页面重新加载但总是崩溃,我必须重新启动mysql才能让应用程序再次运行,它会出现以下错误:

  

2015年4月4日下午12:21:55 com.mchange.v2.resourcepool.BasicResourcePool $ AcquireTask run   警告:com.mchange.v2.resourcepool.BasicResourcePool$AcquireTask@634d8e3d - 获取尝试失败!!!清除待处理的收购。在尝试获取所需的新资源时,我们未能成功超过允许的最大获取尝试次数(30)。上次获取尝试异常:   com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException:数据源拒绝建立连接,来自服务器的消息:"连接太多"       at sun.reflect.GeneratedConstructorAccessor101.newInstance(Unknown Source)       at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)       at java.lang.reflect.Constructor.newInstance(Constructor.java:526)       在com.mysql.jdbc.Util.handleNewInstance(Util.java:411)       在com.mysql.jdbc.Util.getInstance(Util.java:386)       在com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1015)       在com.mysql.jdbc.SQLError.createSQLException(SQLError.java:989)       在com.mysql.jdbc.SQLError.createSQLException(SQLError.java:975)       在com.mysql.jdbc.MysqlIO.doHandshake(MysqlIO.java:1114)       在com.mysql.jdbc.ConnectionImpl.coreConnect(ConnectionImpl.java:2493)       在com.mysql.jdbc.ConnectionImpl.connectOneTryOnly(ConnectionImpl.java:2526)       在com.mysql.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:2311)       在com.mysql.jdbc.ConnectionImpl。(ConnectionImpl.java:834)       在com.mysql.jdbc.JDBC4Connection。(JDBC4Connection.java:47)       at sun.reflect.GeneratedConstructorAccessor43.newInstance(Unknown Source)       at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)       at java.lang.reflect.Constructor.newInstance(Constructor.java:526)       在com.mysql.jdbc.Util.handleNewInstance(Util.java:411)       在com.mysql.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:416)       在com.mysql.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:347)       在com.mchange.v2.c3p0.DriverManagerDataSource.getConnection(DriverManagerDataSource.java:135)       在com.mchange.v2.c3p0.WrapperConnectionPoolDataSource.getPooledConnection(WrapperConnectionPoolDataSource.java:182)       在com.mchange.v2.c3p0.WrapperConnectionPoolDataSource.getPooledConnection(WrapperConnectionPoolDataSource.java:171)       at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool $ 1PooledConnectionResourcePoolManager.acquireResource(C3P0PooledConnectionPool.java:137)       在com.mchange.v2.resourcepool.BasicResourcePool.doAcquire(BasicResourcePool.java:1014)       在com.mchange.v2.resourcepool.BasicResourcePool.access $ 800(BasicResourcePool.java:32)       在com.mchange.v2.resourcepool.BasicResourcePool $ AcquireTask.run(BasicResourcePool.java:1810)       在com.mchange.v2.async.ThreadPerTaskAsynchronousRunner $ TaskThread.run(ThreadPerTaskAsynchronousRunner.java:255)

以下是提供问题背景的相关文件/配置:

spring.xml:

<context:component-scan base-package="com.clathrop.infographyl.dao" />

<bean id="entityManagerFactory"
      class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="persistenceUnitName" value="infographylPU" />
    <property name="dataSource" ref="dataSource" />
</bean>

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
    <!-- Connection properties -->
    <property name="driverClass" value="com.mysql.jdbc.Driver" />
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/infographyl_db" />
    <property name="user" value="user" />
    <property name="password" value="passwd" />
    <!-- Pool properties -->
    <property name="minPoolSize" value="5" />
    <property name="maxPoolSize" value="20" />
    <property name="acquireIncrement" value="1" />
    <property name="maxStatements" value="0" />
    <property name="idleConnectionTestPeriod" value="3000" />
    <property name="loginTimeout" value="300" />
</bean>

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>

的persistence.xml:

<persistence-unit name="infographylPU" transaction-type="RESOURCE_LOCAL">
    <class>com.clathrop.infographyl.model.Infographic</class>
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <properties>
        <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect"/>
    </properties>
</persistence-unit>

这是基本查询的样子,在这个类中我有一个EntityManager作为实例变量,并且在每个查询完成后我关闭()entityManager但它对数据库中建立的连接数没有影响。

InfographicDaoImpl.java:

@Repository
public class InfographicDaoImpl implements InfographicDao{

@PersistenceContext
private EntityManager entityManager;

@Override
@Transactional
public void insertInfographic(Infographic infographic){
    try{
        entityManager.persist(infographic);
    } catch (Exception e){
        e.printStackTrace();
    } finally {
        entityManager.close();
    }

}

@Override
public List<Infographic> findAllInfographics(){
    try{
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        CriteriaQuery<Infographic> cq = builder.createQuery(Infographic.class);

        Root<Infographic> root = cq.from(Infographic.class);
        cq.select(root);
        List<Infographic> igList = entityManager.createQuery(cq).getResultList();
        return igList;
    } catch (Exception e){
        e.printStackTrace();
        return null;
    } finally {
        entityManager.close();
    }
}

@Override
public Infographic getRandomInfographic(Integer tableSize){
    Random rand = new Random();
    int randomIndex = rand.nextInt((tableSize-1)+1) + 1;

    try{
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        CriteriaQuery<Infographic> cq = builder.createQuery(Infographic.class);

        Root<Infographic> root = cq.from(Infographic.class);
        cq.select(root);
        cq.where(builder.equal(root.<Integer>get("id"), randomIndex));
        Infographic randomIg = entityManager.createQuery(cq).getSingleResult();
        return randomIg;
    } catch (Exception e){
        e.printStackTrace();
        return null;
    } finally {
        entityManager.close();
    }


}

@Override
public Integer getRowCount(){
    try{
        Number result = (Number) entityManager.createNativeQuery("Select count(id) from infographics").getSingleResult();
        return result.intValue();
    } catch (Exception e){
        e.printStackTrace();
        return null;
    } finally {
        entityManager.close();
    }
}

@Override
public List<Infographic> listInfographics(Integer startIndex, Integer pageSize){
    List<Infographic> igList = new ArrayList<Infographic>();

    String sStartIndex = Integer.toString(startIndex);
    String sPageSize = Integer.toString(pageSize);

    try{
        List list = entityManager.createNativeQuery("Select * from infographics limit " + sStartIndex + ", " + sPageSize).getResultList();
        for(Object ig : list){
            igList.add((Infographic) ig);
        }
        return igList;
    } catch(Exception e){
        e.printStackTrace();
        return null;
    } finally {
        entityManager.close();
    }


}

}

此时我对我的申请中发生的事情有几个问题。我理解它的方式是c3p0处理与数据库的连接,我认为通过池连接,所做的连接将被重用,但是当我查看mysql中的进程列表时,我得到了越来越多的连接列表。连接数一直在增长,直到应用程序最终抛出上面显示的太多连接警告。我的问题是为什么c3p0不重用连接?我错过了某个地方的配置,告诉它重用已有的连接吗?为什么entityManager.close()似乎没有影响?如果我误解了如何使用hibernate和c3p0,请让我知道我错过了什么。任何帮助都非常感谢,提前谢谢。

更新:

  

严重:Web应用程序[/ infographyl]似乎已经启动了一个名为[com.mchange.v2.async.ThreadPoolAsynchronousRunner $ PoolThread-#0]的线程,但未能阻止它。这很可能会造成内存泄漏。

我已将问题缩小到内存泄漏,当tomcat启动并部署有问题的应用程序时,内存泄漏正在被识别。我现在的问题是试图找出导致问题的可能配置(或缺少配置)。

我目前的解决方法是在my.cnf中设置wait_timeout来杀死超过5秒的进程/连接,这似乎现在还可以,但它不是一个可持续的解决方案,我想知道关闭我的联系的正确方法。

UPDATE2: com.mchange。* INFO日志:

  

2015年4月5日下午10:57:30 org.hibernate.service.jdbc.connections.internal.ConnectionProviderInitiator instantiateExplicitConnectionProvider   信息:HHH000130:实例化显式连接提供程序:org.hibernate.ejb.connection.InjectedDataSourceConnectionProvider   2015年4月5日下午10:57:30 com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource getPoolManager   信息:初始化c3p0池... com.mchange.v2.c3p0.ComboPooledDataSource [acquireIncrement - &gt; 1,acquireRetryAttempts - &gt; 30,acquireRetryDelay - &gt; 1000,autoCommitOnClose - &gt; false,automaticTestTable - &gt; null,breakAfterAcquireFailure - &gt; false,checkoutTimeout - &gt; 0,connectionCustomizerClassName - &gt; null,connectionTesterClassName - &gt; com.mchange.v2.c3p0.impl.DefaultConnectionTester,dataSourceName - &gt; z8kfsx98137ghpr10fde73 | 6aa74262,debugUnreturnedConnectionStackTraces - &gt; false,description - &gt; null,driverClass - &gt; com.mysql.jdbc.Driver,factoryClassLocation - &gt; null,forceIgnoreUnresolvedTransactions - &gt; false,identityToken - &gt; z8kfsx98137ghpr10fde73 | 6aa74262,idleConnectionTestPeriod - &gt; 3000,initialPoolSize - &gt; 3,jdbcUrl - &gt; jdbc:mysql:// localhost:3306 / infographyl_db,lastAcquisitionFailureDefaultUser - &gt; null,maxAdministrativeTaskTime - &gt; 0,maxConnectionAge - &gt; 0,maxIdleTime - &gt; 0,maxIdleTimeExcessConnections - &gt; 0,maxPoolSize - &gt; 20,maxStatements - &gt; 0,maxStatementsPerConnection - &gt; 0,minPoolSize - &gt; 5,numHelperThreads - &gt; 3,numThreadsAwaitingCheckoutDefaultUser - &gt; 0,preferredTestQuery - &gt; null,properties - &gt; {user = ******,password = ******},propertyCycle - &gt; 0,testConnectionOnCheckin - &gt; false,testConnectionOnCheckout - &gt; false,unreturnedConnectionTimeout - &gt; 0,usesTraditionalReflectiveProxies - &gt;错误的]

3 个答案:

答案 0 :(得分:1)

因此,从您描述的所有内容来看,听起来您的应用程序正在创建,然后放弃多个c3p0池。您没有遇到单个池耗尽的常见症状(应用程序冻结)。相反,您的应用程序会打开比maxPoolSize更多的Connections,然后在达到服务器端连接限制时失败。除非你的服务器东西~20个连接太多,否则你可能会创建多个池。设置wait_timeout会隐藏问题,因为放弃的DataSources的连接会自动关闭()编辑,但这不是一个好的解决方案。如果您为每个客户端创建新的DataSource,您将大大减慢而不是加速您的应用程序,如果这些DataSource不是close()ed(它们似乎不是,或者您不会累积打开的Connections) ,你将创建线程和内存泄漏。

所以

首先,你是如何记录的?请确保在INFO中记录com.mchange.*个类,并检查查找池启动消息。在DataSource初始化时,c3p0在INFO上转储大型池配置消息。确保您至少看到其中一条消息。你看过很多次吗?那就是问题所在。

如果我是对的并且您正在打开然后放弃多个c3p0数据源,那么下一个问题就变成了原因。应用程序中的池化数据源嵌入在EntityManagerFactory对象中,在应用程序的生命周期中应该只有一个对象。 Spring使事情变得简单,但它隐藏了如何/何时构造,销毁等等的细节。我认为你可能必须回答的关键问题是为什么Spring是否创建多个EntityManagerFactory实例(或者重新创建c3p0 DataSource多个单个EntityManagerFactory中的时间。)

P.S。 c3p0没有提供“loginTimeout”配置参数。

答案 1 :(得分:0)

我认为,某些数据库操作会导致异常。在这种情况下抛出异常并且EntityManager没有按原样关闭。通常,您的数据库操作应该类似于

        EntityManager em=getEntityManagerSomehow();
        try{
            do your stuff with db here
        }catch(Exception ex){ 
            handle exception here eg. log or rethrow.

          }finally{
           em.close();  // always close entity manager even if exception occures.
       }

在我看来,如果您重构代码如下所示,问题将得到解决。

答案 2 :(得分:0)

我找到了解决问题的方法,但我不太确定这是最好的方法。

我通过以下方式将my.cnf中的wait_timeout设置为5秒:

[mysqld]
wait_timeout=5

这似乎可以有效地破坏entityManager正在创建的休眠进程,并允许我长时间运行应用程序,而无需重新启动mysql。

我现在的问题是,这真的是处理由c3p0 + EntityManager创建的连接的最佳和唯一方法吗?为什么没有c3p0破坏连接或自行删除它们?这是有意的吗?任何澄清或讨论都表示赞赏。谢谢大家:))