mvc - spring java tutorial
Hibernate no libera conexiones del grupo de conexiones (7)
Estoy creando una aplicación con Hibernate JPA y uso c3p0 para la agrupación de conexiones con MySQL. Tengo un problema con el número de conexiones a la base de datos MySQL cuando llega a las 152 conexiones abiertas, esto no es necesario ya que defino en mi archivo de configuración c3p0 el tamaño máximo de la agrupación en 20, y por supuesto cierro cada administrador de entidades que obtengo desde EntityManagerFactory
después de EntityManagerFactory
cada transacción.
Por cada vez que se ejecuta un controlador, noto que se abren más de 7 conexiones y, si actualizo, se vuelven a abrir 7 conexiones sin que se cierren las conexiones pasadas inactivas. Y en cada función DAO que llamo, se ejecuta em.close (). Admito aquí que el problema está en mi código, pero no sé qué estoy haciendo mal aquí.
Esta es la entidad 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;
}
Y aquí está mi clase 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;
}
Como ve, em
está cerrado. En mi JSP, hago esto: sé que esta no es la buena manera de hacer las cosas desde el punto de vista.
<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>
<%
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>
Supongo que cada vez que llamo a user.getType()
, ¿se establece una solicitud? Si es así, ¿cómo puedo evitar esto?
Para el archivo de configuración c4p0, lo incluí en persistence.xml, vi varias publicaciones diciendo que necesito poner el archivo de configuración c3p0 en c3p0-config.xml, pero con mi configuración, el c3p0 se inicializa con los valores que paso en la persistencia archivo .xml, también las conexiones de mysql están llegando a 152 conexiones, pero maxpoolsize
está en 20, aquí está el archivo 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&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>
EDITAR: estoy implementando la aplicación en un servidor de red hat con Tomcat y MySQL instalados. Me pregunto por qué Hibernate está abriendo demasiadas conexiones a MySQL, con todos los administradores de entidades cerrados, ninguna conexión permanecerá abierta, pero este no es el caso. Estoy adivinando y corrígeme si soy cierto que las conexiones se abren cuando hago algo como esto:
List<Sondage> allSondages = SondageDao.getAllSondages();
for (Sondage sondage : allSondages) {
List<Question> questions = sondage.getQuestions();
//code to display questions for example
}
Aquí cuando uso sondage.getQuestions()
, ¿Hibernate abre una conexión a la base de datos y no la cierra después, me falta algo en el archivo de configuración que cierra o devuelve la conexión al grupo cuando termina con ella? Gracias de antemano por cualquier ayuda.
EDIT2: Como la gente está pidiendo versiones, aquí están: 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 Gracias de antemano
La versión mysql es Mysql 5.6.17 si eso puede ayudar ...
EDIT 4: como la gente está confundida acerca de que la versión de bruja del código que publiqué tiene errores, déjeme editar esto para que sepa exactamente lo que sucede:
Primero comenzaré por mostrar cuál es el código erróneo, ya que a ustedes no les importa lo que funciona:
@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;
}
Así que esto es básicamente lo que hice para todas mis funciones de dao, sé que la transacción no es necesaria aquí, ya que vi preguntas que señalaban que las transacciones son importantes para cerrar la conexión. además de esto, obtengo EntityManager de la clase PersistenceManager que tiene un objeto singleton EntityManagerFactory, por lo que getEntityManager crea un entityManager desde el objeto singleton EntityManagerFactory: => code es mejor 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;
}
}
Sí, esto es genial y todo bien, pero ¿dónde está el problema?
El problema aquí es que esta versión abre las conexiones y nunca las cierra, el em.close () no tiene ningún efecto, mantiene la conexión abierta en la base de datos.
El arreglo novato:
Lo que hice para solucionar este problema es crear una EntityManagerFactory para cada solicitud, significa que el dao se ve así:
@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;
}
Ahora esto es malo y lo mantendré mientras no tenga la respuesta para esta pregunta (parece ser: D). Entonces, básicamente con este código Todas las conexiones se cierran después de que hibernate no las necesita. Gracias de antemano por cualquier esfuerzo que hagas en esta pregunta :)
Como sibnick ya respondió las preguntas técnicas, intentaré abordar algunos puntos con los que pareces estar confundido. Así que permítanme darles algunas ideas sobre cómo una aplicación de hibernación y grupo de conexiones está diseñada para funcionar:
- Abrir una conexión de base de datos es una operación "costosa". Para evitar tener que pagar ese costo por cada solicitud, utiliza un grupo de conexiones. El grupo abre una cierta cantidad de conexiones a la base de datos con anticipación y cuando lo necesite puede pedir prestada una de esas conexiones existentes. Al final de la transacción, estas conexiones no se cerrarán sino que se devolverán al grupo para que puedan ser solicitadas en la próxima solicitud. Bajo una gran carga, puede haber muy pocas conexiones para atender todas las solicitudes, por lo que el grupo podría abrir conexiones adicionales que podrían cerrarse más adelante, pero no de una vez.
- La creación de
EntityManagerFactory
es aún más costosa (creará cachés, abrirá un nuevo grupo de conexiones, etc.), así que evite hacerlo por cada solicitud. Sus tiempos de respuesta serán increíblemente lentos. Además, la creación de demasiados EntityManagerFactories podría agotar su espacio PermGen. Así que solo cree unEntityManagerFactory
por aplicación / persistence-context,EntityManagerFactory
al inicio de la aplicación (de lo contrario, la primera solicitud tardará demasiado) y ciérrelo al finalizar la aplicación.
En pocas palabras: cuando use un grupo de conexión, debe esperar que un cierto número de conexiones de DB permanezcan abiertas durante la vida de su aplicación. Lo que no debe suceder es que el número aumente con cada pedido. Si insistes en que las conexiones se cierren al final de la sesión, no uses un grupo y prepárate para pagar el precio.
Creo que Hibernate y C3P0 se están comportando correctamente aquí. De hecho, debería ver que siempre hay al menos tres conexiones abiertas a la base de datos según su configuración C3P0.
Cuando ejecuta una consulta, Hibernate utilizará una conexión del grupo y luego la devolverá cuando finalice. No cerrará la conexión. C3P0 puede reducir el grupo si se excede el tamaño mínimo y se extingue el tiempo de algunas de las conexiones.
En el ejemplo final, verá las conexiones cerradas porque ha cerrado la fábrica de su administrador de entidades y, por lo tanto, su grupo de conexiones.
En mi propiedad de aplicación, tengo un parámetro relacionado con el origen de datos. Los que se dan abajo:
# DataSource Parameter
minPoolSize:5
maxPoolSize:100
maxIdleTime:5
maxStatements:1000
maxStatementsPerConnection:100
maxIdleTimeExcessConnections:10000
Aquí, el valor **maxIdleTime**
es el principal culpable. T toma valor en segundo. Aquí maxIdleTime = 5 significa que después de 5 segundos si la conexión no se está utilizando, se liberará la conexión y tomará la conexión minPoolSize: 5. Aquí maxPoolSize: 100 significa que tomará un máximo de 100 conexiones a la vez.
En mi clase de configuración de DataSource
tengo un bean. Aquí está el código de ejemplo:
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;
}
Espero que esto solucione tu problema :)
Llama a Persistence.createEntityManagerFactory("CAOE")
cada vez. Está mal. Cada llamada createEntityManagerFactory
crea un nuevo grupo de conexiones (indepented). Debería almacenar en caché el objeto EntityManagerFactory en algún lugar.
EDITAR:
También debe cerrar manualmente EntityManagerFactory. Puedes hacerlo en @WebListener:
@WebListener
public class AppInit implements ServletContextListener {
public void contextInitialized(ServletContextEvent sce) {}
public void contextDestroyed(ServletContextEvent sce) {
PersistenceManager.closeEntityMangerFactory();
}
}
De lo contrario, cada caso de redespliegue es fuente de conexiones filtradas.
Me encontré con el mismo problema y pude solucionarlo creando una clase contenedora singleton para EntityManagerFactory y creando EntityManager donde se necesita. Estás teniendo el problema de sobrecarga de conexión porque estás envolviendo la creación de EntityManager en la clase singleton, lo cual es incorrecto. EntityManager proporciona el alcance de la transacción (no se debe reutilizar), EntityManagerFactory proporciona las conexiones (se debe reutilizar).
desde: 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;
}
}
y luego use la instancia de fábrica para crear un EntityManager para cada solicitud.
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import EMF;
// ...
EntityManager em = EMF.get().createEntityManager();
Parece que el problema está relacionado con el error de Hibernate . Intente especificar la estrategia de búsqueda EAGER en sus anotaciones de OneToMany.
@OneToMany(mappedBy = "sondage", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
Puedes probar lo siguiente:
<property name="hibernate.connection.release_mode" value="after_transaction" />
<property name="hibernate.current_session_context_class" value="jta" />
en lugar de tu modo de lanzamiento actual?