Hibernate 不释放连接池中的连接

Posted

技术标签:

【中文标题】Hibernate 不释放连接池中的连接【英文标题】:Hibernate not releasing connections from connection pool 【发布时间】:2015-12-04 06:54:00 【问题描述】:

我正在使用 Hibernate JPA 创建一个应用程序,并使用 c3p0 与 mysql 进行连接池。我对 MySQL 数据库的连接数有疑问,因为它达到了 152 个打开的连接,这是不想要的,因为我在我的 c3p0 配置文件中将最大池大小定义为 20,当然我关闭了我得到的每个实体管理器提交每个事务后来自EntityManagerFactory

每次执行控制器时,我注意到打开了超过 7 个连接,如果我刷新,则再次打开 7 个连接,而不会关闭过去的空闲连接。在我调用的每个 DAO 函数中,都会执行 em.close()。我在这里承认问题出在我的代码中,但我不知道我在这里做错了什么。

这是 Sondage.java 实体:

@Entity
@NamedQuery(name="Sondage.findAll", query="SELECT s FROM Sondage s")
public class Sondage implements Serializable 

    private static final long serialVersionUID = 1L;

    public Sondage() 

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    private String name;

    private byte needLocation;

    //bi-directional many-to-one association to ResultatSondage
    @OneToMany(mappedBy = "sondage", cascade = CascadeType.ALL)
    @OrderBy("sondage ASC")
    private List<ResultatSondage> resultatSondages;

    //bi-directional many-to-one association to SondageSection
    @OneToMany(mappedBy = "sondage", cascade = CascadeType.ALL)
    private List<SondageSection> sondageSections;

这是我的 DAO 类:

@SuppressWarnings("unchecked")
public static List<Sondage> GetAllSondage() 
    EntityManager em = PersistenceManager.getEntityManager();
    List<Sondage> allSondages = new ArrayList<>();
    try 
        em.getTransaction().begin();
        Query query = em.createQuery("SELECT s FROM Sondage s");
        allSondages = query.getResultList();
        em.getTransaction().commit();
     catch (Exception ex) 
        if (em.getTransaction().isActive()) 
            em.getTransaction().rollback();
        
        allSondages = null;
     finally 
        em.close();
    
    return allSondages;

如您所见,em 已关闭。在我的 JSP 中,我这样做:我知道这不是在视图方面做事的好方法。

<body>
    <div class="header">
        <%@include file="../../../Includes/header.jsp" %>
    </div>
    <h2 style="color: green; text-align: center;">الاستمارات</h2>
    <div id="allsurveys" class="pure-menu custom-restricted-width">
        <%
            List<Sondage> allSondages = (List<Sondage>) request.getAttribute("sondages");

            for (int i = 0; i < allSondages.size(); i++) 
        %>
        <a  href="$pageContext.request.contextPath /auth/dosurvey?id=<%= allSondages.get(i).getId()%>"><%= allSondages.get(i).getName()%></a> &nbsp;
        <%
            if (request.getSession().getAttribute("user") != null) 
                Utilisateur user = (Utilisateur) request.getSession().getAttribute("user");
                if (user.getType().equals("admin")) 
        %>
        <a href="$pageContext.request.contextPath /aauth/editsurvey?id=<%= allSondages.get(i).getId()%>">تعديل</a>
        <%
                
            
        %>
        <br />
        <%
            
        %>
    </div>
</body>

我猜每次我打电话给user.getType(),都会建立一个请求?如果是这样,我该如何防止这种情况发生?

对于 c4p0 配置文件,我将其包含在 persistence.xml 中,我看到一些帖子说我需要将 c3p0 配置文件放在 c3p0-config.xml 中,但是通过我的设置,c3p0 使用我传递的值初始化在 persistence.xml 文件中,mysql 连接数也达到 152 个,但 maxpoolsize 为 20,这是 persistence.xml 文件

<persistence version="2.1"
             xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
             http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">

    <persistence-unit name="CAOE" transaction-type="RESOURCE_LOCAL">
        <class>com.caoe.Models.ChoixQuestion</class>
        <class>com.caoe.Models.Question</class>
        <class>com.caoe.Models.Reponse</class>
        <class>com.caoe.Models.ResultatSondage</class>
        <class>com.caoe.Models.Section</class>
        <class>com.caoe.Models.Sondage</class>
        <class>com.caoe.Models.SondageSection</class>
        <class>com.caoe.Models.SousQuestion</class>
        <class>com.caoe.Models.Utilisateur</class>
        <properties>
            <property name="hibernate.connection.provider_class"
                      value=" org.hibernate.service.jdbc.connections.internal.C3P0ConnectionProvider" />

            <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
            <property name="hibernate.connection.password" value=""/>

            <property name="hibernate.connection.url"
                      value="jdbc:mysql://localhost:3306/caoe?useUnicode=yes&amp;characterEncoding=UTF-8"/>

            <property name="hibernate.connection.username" value="root"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
            <property name="hibernate.show_sql" value="true" />

            <property name="hibernate.c3p0.max_size" value="50" />
            <property name="hibernate.c3p0.min_size" value="3" />
            <property name="hibernate.c3p0.max_statements" value="20" />
            <property name="hibernate.c3p0.acquire_increment" value="1" />
            <property name="hibernate.c3p0.idle_test_period" value="30" />
            <property name="hibernate.c3p0.timeout" value="35" />
            <property name="hibernate.c3p0.checkoutTimeout" value="60000" />
            <property name="hibernate.connection.release_mode" value="after_statement" />

            <property name="debugUnreturnedConnectionStackTraces"
                      value="true" />
        </properties>
    </persistence-unit>
</persistence>

编辑:我正在将应用程序部署到安装了 Tomcat 和 MySQL 的红帽服务器。我只是想知道为什么 Hibernate 打开太多与 MySQL 的连接,所有实体管理器都关闭,没有连接将保持打开状态,但事实并非如此。如果我确实在执行以下操作时打开了连接,我正在猜测并纠正我:

List<Sondage> allSondages = SondageDao.getAllSondages();

for (Sondage sondage : allSondages) 
    List<Question> questions = sondage.getQuestions();
    //code to display questions for example

在这里,当我使用sondage.getQuestions() 时,Hibernate 是否打开与数据库的连接并且之后不关闭它,我是否在配置文件中遗漏了一些在完成后关闭或返回与池的连接的内容。提前感谢您的帮助。

编辑2: 由于人们要求版本,因此它们是: JAVA jre 1.8.0_25 Apache Tomcat v7.0 休眠核心4.3.10 休眠 c3p0 4.3.10.final 休眠-jpa 2.1 提前致谢

如果有帮助的话,mysql版本是Mysql 5.6.17...

编辑 4:由于人们对我发布的代码的女巫版本感到困惑,所以让我编辑它,以便您知道究竟发生了什么:

首先,我将首先展示什么是错误代码,因为你们并不关心什么是有效的:

@SuppressWarnings("unchecked")
public static List<Sondage> GetAllSondage() 
    EntityManager em = PersistenceManager.getEntityManager();
    List<Sondage> allSondages = new ArrayList<>();
    try 
       em.getTransaction().begin();
       Query query = em.createQuery("SELECT s FROM Sondage s");
       allSondages = query.getResultList();
       em.getTransaction().commit();
     catch (Exception ex) 
    if (em.getTransaction().isActive()) 
        em.getTransaction().rollback();
    
    allSondages = null;
     finally 
        em.close();
    
    return allSondages;
  

所以这基本上是我为所有 dao 函数所做的,我知道这里不需要事务,因为我看到问题指出事务对于关闭连接很重要。除此之外,我从具有 EntityManagerFactory 单例对象的 PersistenceManager 类中获取实体管理器,因此 getEntityManager 从 EntityManagerFactory 单例对象创建一个实体管理器:=> 代码优于 1000 字: PesistenceManager.java:

import javax.persistence.EntityManager;
    import javax.persistence.EntityManagerFactory;
    import javax.persistence.Persistence;

    public class PersistenceManager 
    
    private static EntityManagerFactory emf = null;

    public static EntityManager getEntityManager()
    
        return getEntityManagerFactory().createEntityManager();     
    

    public static EntityManagerFactory getEntityManagerFactory()
    
            if(emf == null) 
                    emf = Persistence.createEntityManagerFactory("CAOE");
                    return emf;
        
            else
                    return emf;
        

是的,这很酷,一切都很好,但问题出在哪里?

这里的问题是这个版本打开连接并且从不关闭它们,em.close()没有效果,它保持连接到数据库。

菜鸟修复:

我为解决这个问题所做的是为每个请求创建一个 EntityManagerFactory,这意味着 dao 看起来像这样:

    @SuppressWarnings("unchecked")
public static List<Sondage> GetAllSondage() 
    //this is the method that return the EntityManagerFactory Singleton Object
    EntityManagerFactory emf = PersistenceManager.getEntitManagerFactory();
    EntityManager em = emf.createEntityManager();
        List<Sondage> allSondages = new ArrayList<>();
        try 
            em.getTransaction().begin();
            Query query = em.createQuery("SELECT s FROM Sondage s");
            allSondages = query.getResultList();
            em.getTransaction().commit();
     catch (Exception ex) 
        if (em.getTransaction().isActive()) 
            em.getTransaction().rollback();
        
        allSondages = null;
         finally 
        em.close();
        emf.close();
    
    return allSondages;

现在这很糟糕,我会在我没有回答这个问题的时候保留它(看起来永远是 :D)。因此,使用此代码基本上所有连接都会在休眠后关闭不需要它们。提前感谢您在这个问题上所做的任何努力:)

【问题讨论】:

注意到您在 persistence.xml 文件中指定了发布模式。根据这个文档:docs.jboss.org/hibernate/stable/core.old/reference/en/html/… 这样做通常是个坏主意。可能从文件中删除该配置行即可。这更多是通过观察和 SWAG 而不是答案,所以这就是为什么我提供它是评论而不是答案。 嗨@MattCampbell,非常感谢您的回答,我没有在配置文件中这样做,但是当我看到属性时,我认为这可以释放一些与池的连接,但这并没有,所以回答你的猜测,这不是问题。 hibernate、JPA、tomcat 和 JAVA 使用哪些版本?你用 JPA 生成你的类吗? 能否请您分享 PersistenceManager 类的详细信息? @rogerdpack 谢谢你,那是 6 年前的事了 :D 是的,问题出在 lib 版本中 【参考方案1】:

由于 sibnick 已经回答了技术问题,我将尝试解决一些您似乎感到困惑的问题。所以让我给你一些关于休眠应用程序和连接池如何工作的想法:

    打开数据库连接是一项“昂贵”的操作。为了避免为每个请求支付费用,您使用连接池。池预先打开一定数量的数据库连接,当您需要一个时,您可以借用这些现有连接之一。在事务结束时,这些连接不会关闭,而是返回到池中,以便下一个请求可以借用它们。在重负载下,连接可能太少而无法处理所有请求,因此池可能会打开其他连接,这些连接可能稍后会关闭,但不会立即关闭。 创建EntityManagerFactory 的成本甚至更高(它会创建缓存、打开新的连接池等),因此请务必避免为每个请求都这样做。您的响应时间将变得异常缓慢。此外,创建太多 EntityManagerFactories 可能会耗尽您的 PermGen 空间。所以只为每个应用程序/持久性上下文创建一个EntityManagerFactory,在应用程序启动时创建它(否则第一个请求将花费太长时间)并在应用程序关闭时关闭它。

底线:使用连接池时,您应该期望一定数量的 DB 连接在应用程序的生命周期内保持打开状态。不能发生的情况是,这个数字会随着每个请求而增加。如果您坚持在会话结束时关闭连接,请不要使用池并准备付出代价。

【讨论】:

谢谢piet.t,不过这一切我都已经知道了,打开的连接并不是连接池所需要的。证明152个打开的连接,我的服务器配置只支持151【参考方案2】:

在我的应用程序属性中,我有一些与数据源相关的参数。这些在下面给出:

# DataSource Parameter
minPoolSize:5
maxPoolSize:100
maxIdleTime:5
maxStatements:1000
maxStatementsPerConnection:100
maxIdleTimeExcessConnections:10000

这里,**maxIdleTime** 值是罪魁祸首。 It 排在第二位。这里的 maxIdleTime=5 表示 5 秒后如果连接没有使用,那么它将释放连接并使用 minPoolSize:5 连接。这里的 maxPoolSize:100 表示一次最多需要 100 个连接。

在我的DataSource 配置类中,我有一个 bean。下面是示例代码:

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.core.env.Environment;
import org.springframework.beans.factory.annotation.Autowired;

@Autowired
    private Environment env;

 @Bean
    public ComboPooledDataSource dataSource()
        ComboPooledDataSource dataSource = new ComboPooledDataSource();

        try 
            dataSource.setDriverClass(env.getProperty("db.driver"));
            dataSource.setJdbcUrl(env.getProperty("db.url"));
            dataSource.setUser(env.getProperty("db.username"));
            dataSource.setPassword(env.getProperty("db.password"));
            dataSource.setMinPoolSize(Integer.parseInt(env.getProperty("minPoolSize")));
            dataSource.setMaxPoolSize(Integer.parseInt(env.getProperty("maxPoolSize")));
            dataSource.setMaxIdleTime(Integer.parseInt(env.getProperty("maxIdleTime")));
            dataSource.setMaxStatements(Integer.parseInt(env.getProperty("maxStatements")));
            dataSource.setMaxStatementsPerConnection(Integer.parseInt(env.getProperty("maxStatementsPerConnection")));
            dataSource.setMaxIdleTimeExcessConnections(10000);

         catch (PropertyVetoException e) 
            e.printStackTrace();
        
        return dataSource;
    

希望这能解决你的问题:)

【讨论】:

【参考方案3】:

您每次都拨打Persistence.createEntityManagerFactory("CAOE")。这是错误的。每次调用 createEntityManagerFactory 都会创建新的(独立的)连接池。您应该在某处缓存 EntityManagerFactory 对象。

编辑:

您还应该手动关闭 EntityManagerFactory。你可以在@WebListener 中做到这一点:

@WebListener
public class AppInit implements ServletContextListener 

    public void contextInitialized(ServletContextEvent sce) 

    public void contextDestroyed(ServletContextEvent sce) 
         PersistenceManager.closeEntityMangerFactory();
    

否则每个重新部署的情况都是泄漏连接的来源。

【讨论】:

嗨 sibnick,抱歉,这段代码是我为解决这个问题而编写的,我基本上为每个请求实例化了实体管理器工厂,这解决了问题,在我的原始代码中,emf 是一个单例, getEntityManagerFactory() 将检查 this.emf 是否为 null 并创建一个新的,如果它不为 null 它将返回 this.emf,我会更新问题,对此感到抱歉 您不会在方法 getEntityManagerFactory 中将新对象存储到 emf 变量中。此外,此代码不是多线程安全的。 您能否确认问题仍然存在(修复 getEntityManagerFactory 后)? getEntityManagerFactory 是正确的,为什么我在编辑问题时复制粘贴,是我每次执行请求时创建的新版本和 getEntityManagerFactory() 实例,这解决了问题,(不再睡觉连接未释放到mysql),但正如你所说,这是错误的。因此,如果我执行当前 EDIT 中的操作,并使用 entityManager(而不是 EntityManagerFactory)来执行 getSondage 或其他任何操作,即使我像问题中所示那样执行 em.close(),连接也不会关闭。 现在我可以在您的帖子中看到正确的代码(除了多线程单例问题 - 所有 JSP/Servlet 应用程序在现实生活中都是多线程的)。只有一件事我不清楚。你在哪里打电话closeEntityMangerFactory?您应该从应用程序侦听器中调用它。否则,每个重新部署案例都是泄漏连接的来源。【参考方案4】:

我遇到了同样的问题,并且能够通过为 EntityManagerFactory 创建一个单例包装类并在需要的地方创建 EntityManager 来解决它。您遇到了连接过载问题,因为您将 EntityManager 创建包装在单例类中,这是错误的。 EntityManager 提供事务范围(不应重复使用),EntityManagerFactory 提供连接(应重复使用)。

来自:https://cloud.google.com/appengine/docs/java/datastore/jpa/overview

import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public final class EMF 
    private static final EntityManagerFactory emfInstance =
        Persistence.createEntityManagerFactory("CAOE");

private EMF() 

public static EntityManagerFactory get() 
    return emfInstance;
    

然后使用工厂实例为每个请求创建一个EntityManager。

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import EMF;

// ...
EntityManager em = EMF.get().createEntityManager();

【讨论】:

【参考方案5】:

我认为 Hibernate 和 C3P0 在这里表现正确。事实上,您应该看到,根据您的 C3P0 配置,始终至少有三个连接到数据库打开。

当您执行查询时,Hibernate 将使用池中的连接,然后在完成时返回它。它不会关闭连接。如果超过最小大小并且某些连接超时,C3P0 可能会缩小池。

在最后一个示例中,您看到连接已关闭,因为您关闭了实体管理器工厂,因此也关闭了连接池。

【讨论】:

完全同意,即使执行 em.close(),C3P0 也会保持连接打开。为什么?很简单,因为这是它的目的 嗨,Alex,感谢您的回答,我知道 C3P0 正在维护连接,供之后请求它的人使用,但是在这里,连接不会释放到池中,请阅读我的问题的标题 嗨,Alex,C3P0 配置为只打开 30 个或类似的东西,当我监控我的 Mysql 服务器时,打开了 152 个连接。所以不正常 这显然不是我问题的答案,所以系统很烂:///【参考方案6】:

您可以尝试以下方法吗:

<property name="hibernate.connection.release_mode" value="after_transaction" />
<property name="hibernate.current_session_context_class" value="jta" />

而不是您当前的发布模式?

【讨论】:

你好,Norbert,我试过这个,即使在文档中,他们指出这是一个坏习惯,但没有。【参考方案7】:

看起来问题与 Hibernate bug 有关。请尝试在 OneToMany 注释中指定获取策略 EAGER。

@OneToMany(mappedBy = "sondage", cascade = CascadeType.ALL, fetch = FetchType.EAGER)

【讨论】:

嗨 emamedov,我需要 FetchType 是 LAZY,因为我所有的实体都依赖它,当我添加一个包含许多问题的部分的调查时,我只做 SurveyDAO.addSurvey 和所有数据库中没有的问题和部分仅通过一次 DAO 调用自动添加,因此将 FetchType 更改为 Eager for me 正在重新构建应用程序 嗨,瑞达。 FetchType 不应影响插入数据库。您仍然可以使用一种方法调用,hibernate 将自行添加整个对象图。但我同意 LAZY 类型可能更可取。我提出解决方案只是为了检查它是否是 Hibernate 错误。如果是,我们会考虑使用 EAGER 类型或找到 LAZY 类型的解决方法

以上是关于Hibernate 不释放连接池中的连接的主要内容,如果未能解决你的问题,请参考以下文章

web java -- 连接池 -- 概述

连接池

使用 Spring 的 Tomcat 数据源池中的所有连接都处于活动状态

DBCP连接池介绍

DBCP连接池介绍

解析ABP框架中的事务处理和工作单元,ABP事务处理