web-dev-qa-db-fra.com

Hibernate ne libère pas les connexions du pool de connexions

Je crée une application avec Hibernate JPA et j'utilise c3p0 pour le regroupement de connexions avec MySQL. J'ai un problème avec le nombre de connexions à la base de données MySQL car il frappe les 152 connexions ouvertes, ce n'est pas souhaitable car je définis dans mon fichier de configuration c3p0 la taille maximale du pool à 20, et bien sûr je ferme tous les gestionnaires d'entités que je reçois. depuis la EntityManagerFactory après avoir validé chaque transaction.

À chaque fois qu'un contrôleur est exécuté, je remarque que plus de 7 connexions sont ouvertes et, si j'actualise, 7 connexions sont rouvertes sans que les connexions inactives antérieures soient fermées. Et dans chaque fonction DAO que j'appelle, la fonction em.close () est exécutée. J'admets ici que le problème est dans mon code, mais je ne sais pas ce que je fais mal ici.

C'est l'entité 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;
}

Et voici mon cours 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;
}

Comme vous le voyez, em est fermé. Dans mon JSP, je fais ceci: je sais que ce n’est pas la bonne façon de faire les choses dans la vue.

<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>

Je suppose que chaque fois que j'appelle user.getType(), une demande est établie? Si oui, comment puis-je empêcher cela?

Pour le fichier de configuration c4p0, je l’ai inclus dans le fichier persistence.xml; j’ai lu plusieurs articles disant que je devais mettre le fichier de configuration c3p0 dans c3p0-config.xml, mais avec ma configuration, c3p0 est initialisé avec les valeurs que je transmets dans la persistance. fichier .xml, les connexions mysql atteignent actuellement 152 connexions mais la variable maxpoolsize est à 20, voici le fichier 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>

EDIT: Je déploie l'application sur un serveur Red Hat avec Tomcat et MySQL Installed. Je me demande simplement pourquoi Hibernate ouvre trop de connexions à MySQL, alors que tous les gestionnaires d'entités sont fermés, aucune connexion ne restera ouverte, mais ce n'est pas le cas. Je devine et corrige-moi si je suis vrai que les connexions sont ouvertes lorsque je fais quelque chose comme ceci: 

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

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

Ici, lorsque j'utilise sondage.getQuestions(), Hibernate ouvre-t-il une connexion à la base de données et ne la ferme-t-il pas après, manque-t-il dans le fichier de configuration quelque chose qui ferme ou renvoie la connexion au pool une fois terminé? Merci d'avance pour votre aide.

EDIT2: Puisque les utilisateurs demandent des versions, les voici: Java jre 1.8.0_25 Apache Tomcat v7.0 Hibernate-core-4.3.10 Hibernate c3p0 4.3.10 .final hibernate-jpa 2.1 Merci d'avance

La version de MySQL est Mysql 5.6.17 si cela peut aider ...

EDIT 4: alors que les gens sont de plus en plus confus au sujet de la version du code que j’ai posté est boguée, laissez-moi l’éditer afin que vous sachiez ce qui se passe exactement:

Tout d'abord, je vais commencer par montrer quel est le code du buggy, car vous vous moquez de ce qui fonctionne:

@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;
  }

C'est donc essentiellement ce que j'ai fait pour toutes mes fonctions de dao. Je sais que la transaction n'est pas nécessaire ici, car j'ai vu des questions indiquant que les transactions sont importantes pour la fermeture de la connexion. A côté de cela, je reçois getEntityManager de la classe PersistenceManager qui a un objet singleton EntityManagerFactory. Donc, getEntityManager crée un entityManager à partir de l'objet singleton EntityManagerFactory: => le code vaut mieux que 1000 Word: 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;
        }
}

Oui c'est cool et tout va bien, mais où est le problème?

Le problème ici est que cette version ouvre les connexions et ne les ferme jamais, la em.close () n’a aucun effet, elle garde la connexion ouverte à la base de données.

Le correctif noob:

Ce que j’ai fait pour résoudre ce problème, c’est de créer un EntityManagerFactory pour chaque requête, cela signifie que le dao ressemble à ceci: 

    @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;
}

Maintenant, c'est mauvais et je vais le garder tant que je n'ai pas de réponse à cette question (ça ressemble à forver: D). Donc, avec ce code, toutes les connexions se ferment après que l'hibernation n'en a plus besoin. Merci d'avance pour tous les efforts que vous avez mis dans cette question :) 

21
Reda

Je pense que Hibernate et C3P0 se comportent correctement ici. En fait, vous devriez voir qu'il y a toujours au moins trois connexions à la base de données ouvertes selon votre configuration C3P0.

Lorsque vous exécutez une requête, Hibernate utilisera une connexion du pool, puis la renverra à la fin. Cela ne fermera pas la connexion. C3P0 peut réduire la taille du pool si la taille minimale est dépassée et que certaines des connexions expirent.

Dans votre dernier exemple, vous voyez que les connexions sont fermées car vous avez arrêté votre fabrique de gestionnaires d'entités et donc votre pool de connexions.

10
Alex Barnes

Vous appelez Persistence.createEntityManagerFactory("CAOE") à chaque fois. Il est faux. Chaque appel createEntityManagerFactory crée un nouveau pool de connexions (indépendant). Vous devez mettre l'objet EntityManagerFactory en cache quelque part.

MODIFIER:

Vous devez également arrêter manuellement EntityManagerFactory. Vous pouvez le faire dans @WebListener:

@WebListener
public class AppInit implements ServletContextListener {

    public void contextInitialized(ServletContextEvent sce) {}

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

Sinon, chaque cas de redéploiement est source de connexions non étanches.

7
sibnick

Pouvez-vous essayer ce qui suit:

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

au lieu de votre mode de publication actuel?

3
Norbert van Nobelen

Puisque sibnick a déjà répondu aux questions techniques, je vais essayer de répondre à certains points qui vous semblent confus. Laissez-moi vous donner quelques idées sur le fonctionnement d’une application et d’un pool de connexions en veille prolongée:

  1. Ouvrir une connexion à une base de données est une opération "coûteuse". Afin d'éviter de devoir payer ce coût pour chaque demande, vous utilisez un pool de connexions. Le pool ouvre un certain nombre de connexions à la base de données à l'avance et, si vous en avez besoin, vous pouvez emprunter l'une de ces connexions existantes. À la fin de la transaction, ces liens ne seront pas fermés, mais renvoyés au pool pour pouvoir être empruntés à la demande suivante. Sous une charge importante, le nombre de connexions peut être insuffisant pour traiter toutes les demandes. Le pool peut donc ouvrir des connexions supplémentaires qui pourraient éventuellement être fermées ultérieurement, mais pas en une fois.
  2. Créer une EntityManagerFactory est encore plus coûteux (cela créera des caches, ouvrira un nouveau pool de connexions, etc.), évitez donc de le faire pour chaque requête. Vos temps de réponse deviendront extrêmement lents. Créer trop d’EntityManagerFactories peut également épuiser votre espace PermGen. Donc, créez seulement une EntityManagerFactory par application/persistence-context, créez-la au démarrage de l'application (sinon la première requête prendra trop de temps) et fermez-la à l'arrêt de l'application.

Conclusion: lors de l'utilisation d'un pool de connexions, vous devez vous attendre à ce qu'un certain nombre de connexions à la base de données restent ouvertes pendant toute la durée de vie de votre application. Ce qui ne doit pas arriver, c'est que le nombre augmente à chaque demande. Si vous insistez pour que les connexions soient fermées à la fin de la session, n'utilisez pas de pool et soyez prêt à en payer le prix.

1
piet.t

Dans ma propriété d'application, j'ai un paramètre lié à la source de données. Ceux-ci sont donnés ci-dessous:

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

Ici, la valeur **maxIdleTime** est le principal responsable. I t prend la valeur en seconde. Ici, maxIdleTime = 5 signifie qu'après 5 secondes, si la connexion n'est pas utilisée, la connexion sera libérée et la connexion minPoolSize: 5 sera établie. MaxPoolSize: 100 signifie qu'il faudra au maximum 100 connexions à la fois.

Dans ma classe de configuration DataSource, j'ai un haricot. Voici l'exemple de code:

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;
    }

J'espère que cela résoudra votre problème :)

0
Md. Sajedul Karim

J'ai rencontré le même problème et j'ai pu le résoudre en créant une classe wrapper singleton pour EntityManagerFactory et en créant EntityManager là où cela était nécessaire. Vous rencontrez le problème de surcharge de connexion car vous encapsulez la création de EntityManager dans la classe singleton, ce qui est faux. EntityManager fournit l'étendue de la transaction (ne doit pas être réutilisé), EntityManagerFactory fournit les connexions (doit être réutilisé).

à partir de: 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;
    }
}

puis utilisez l’instance d’usine pour créer un EntityManager pour chaque demande.

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

// ...
EntityManager em = EMF.get().createEntityManager();
0
Willem van Rooyen