2010-05-03 13 views
13

Necesito hacer que mi aplicación web funcione con enormes conjuntos de datos. En este momento recibo OutOfMemoryException o la salida que se genera de 1 a 2 minutos.¿Cómo manejar grandes conjuntos de datos con JPA (o al menos con Hibernate)?

Pongamos simple y supongamos que tenemos 2 tablas en DB: Worker y WorkLog con aproximadamente 1000 filas en la primera y 10 000 000 filas en la segunda. Última tabla tiene varios campos, incluidos los campos 'workerId' y 'hoursWorked', entre otros. Lo que necesitamos es:

  1. cuente las horas totales trabajadas por cada usuario;

  2. lista de períodos de trabajo para cada usuario.

El enfoque más directo (OMI) para cada tarea en SQL sin formato es:

1)

select Worker.name, sum(hoursWorked) from Worker, WorkLog 
    where Worker.id = WorkLog.workerId 
    group by Worker.name; 

//results of this query should be transformed to Multimap<Worker, Long> 

2)

select Worker.name, WorkLog.start, WorkLog.hoursWorked from Worker, WorkLog 
    where Worker.id = WorkLog.workerId; 

//results of this query should be transformed to Multimap<Worker, Period> 
//if it was JDBC then it would be vitally 
//to set resultSet.setFetchSize (someSmallNumber), ~100 

lo tanto, tengo dos preguntas :

  1. cómo implementar cada uno de mis enfoques con JPA (o al menos con Hibernate);
  2. ¿cómo manejarías este problema (con JPA o Hibernate por supuesto)?
+1

¿Está tratando de crear un informe, o está tratando de cargar un montón de objetos? Si solo está tratando de crear un informe, hágalo en SQL como dijo y termine con él. – Zak

+0

@Zak: Tengo una aplicación web en jpa + spring + jsf que funciona. Pero su desempeño debería ser mejor. Y, lo que es más importante, debería ser capaz de manejar conjuntos de datos mucho más grandes de lo que puede manejar en este momento. 1) Hay un problema con la primera consulta que no sé cómo escribirla en 'hql' o' jpa query language'. No quiero usar sql simple, es un último recurso. 2) El problema con la segunda consulta es que no sé cómo configurar el tamaño de búsqueda en 'JPA' y tampoco sé cómo manejar esta situación con' JPA': no hay ningún bucle en el conjunto de resultados, no lo hago Sé cómo cargar la próxima búsqueda. – Roman

Respuesta

13

supongamos que tenemos 2 mesas en el PP: Trabajador y registro de trabajo con cerca de 1.000 filas en el primero y 10 000 000 filas de la segunda

para grandes volúmenes de este tipo, mi la recomendación sería utilizar The StatelessSession interface de Hibernate:

Alternativamente, Hibernate proporciona una API orientada a comandos- que se puede utilizar para la transmisión de datos hacia y desde la base de datos en forma de objetos separados . Un StatelessSession no tiene contexto de persistencia asociado con él y no proporciona muchos de los semánticas de ciclo de vida de nivel superior. En en particular, una sesión sin estado hace no implementar un caché de primer nivel ni interactuar con cualquier caché de consultas de segundo nivel o . No implementa write-behind transaccional o comprobación sucia automática. Las operaciones realizadas con una sesión sin estado nunca se conectan en cascada a las instancias asociadas. Las colecciones son ignoradas por una sesión stateless . Las operaciones realizadas a través de una sesión sin estado omiten el modelo de evento de Hibernate e interceptores. Debido a la falta de un caché de primer nivel, Las sesiones sin estado son vulnerables a los efectos de alias de datos . Una sesión sin estado es una abstracción de nivel inferior que está mucho más cerca del JDBC subyacente.

StatelessSession session = sessionFactory.openStatelessSession(); 
Transaction tx = session.beginTransaction(); 

ScrollableResults customers = session.getNamedQuery("GetCustomers") 
    .scroll(ScrollMode.FORWARD_ONLY); 
while (customers.next()) { 
    Customer customer = (Customer) customers.get(0); 
    customer.updateStuff(...); 
    session.update(customer); 
} 

tx.commit(); 
session.close(); 

En este ejemplo de código, los Customer casos devueltos por la consulta se separan inmediatamente. Nunca son asociados con cualquier contexto de persistencia .

Los insert(), update() y delete() operaciones definidas por la interfaz StatelessSession son considerado como la base de datos operaciones directas de nivel de fila. Resultan en la ejecución inmediata de un SQL INSERT, UPDATE o DELETE respectivamente. Tienen una semántica diferente de en las operaciones save(), saveOrUpdate() y delete() definidas por la interfaz Session .

+0

@Pascal Thivent: ¡gracias por la respuesta! Acerca de los volúmenes: no conozco los volúmenes reales, solo especifiqué el máximo (en mi opinión, que se basa en cierto conocimiento del dominio). Tal vez el volumen real es 10-100 veces menor y en mi humilde opinión la solución para estos volúmenes también estará bien. – Roman

+0

¿Sabe a qué se refieren exactamente por "las sesiones sin estado son vulnerables a los efectos de alias de datos"? Gracias. –

+0

Esto no es de ninguna manera más rápido.De hecho, es ** extremadamente ** lento y mucho menos eficaz que el uso habitual de 'EntityManager'. – Blauhirn

1

Raw SQL no debe considerarse un último recurso. Todavía se debe considerar una opción si desea mantener las cosas "estándar" en el nivel de JPA, pero no en el nivel de la base de datos. JPA también tiene soporte para consultas nativas donde aún hará la asignación a las entidades estándar por usted.

Sin embargo, si tiene un conjunto de resultados grande que no se puede procesar en la base de datos, entonces debería usar JDBC simple ya que JPA (estándar) no admite la transmisión de grandes conjuntos de datos.

Será más difícil portar la aplicación en diferentes servidores de aplicaciones si utiliza construcciones específicas de implementación JPA ya que el motor JPA está incrustado en el servidor de aplicaciones y es posible que no tenga control sobre el proveedor JPA.

+0

esto. Encontré que ejecutar manualmente una consulta de conexión ('session.doWork' o similar) es de hecho el más rápido que puedes obtener – Blauhirn

+0

el estándar EntityManager no tiene operación' doWork'. –

+0

sí, es por eso que escribí 'session' que puede obtener a través de' entityManager.unwrap (Session.class); '. Idk si eso es un mal estilo de programación. Supongo que también se podría escribir un 'sessionFactory' Bean – Blauhirn

0

Estoy usando algo como esto y funciona muy rápido. También odio usar SQL nativo ya que nuestra aplicación debería funcionar en cualquier base de datos.

Folowing se reubica en un sql muy optimizado y devuelve la lista de registros que son mapas.

String hql = "select distinct " + 
      "t.uuid as uuid, t.title as title, t.code as code, t.date as date, t.dueDate as dueDate, " + 
      "t.startDate as startDate, t.endDate as endDate, t.constraintDate as constraintDate, t.closureDate as closureDate, t.creationDate as creationDate, " + 
      "sc.category as category, sp.priority as priority, sd.difficulty as difficulty, t.progress as progress, st.type as type, " + 
      "ss.status as status, ss.color as rowColor, (p.rKey || ' ' || p.name) as project, ps.status as projectstatus, (r.code || ' ' || r.title) as requirement, " + 
      "t.estimate as estimate, w.title as workgroup, o.name || ' ' || o.surname as owner, " + 
      "ROUND(sum(COALESCE(a.duration, 0)) * 100/case when ((COALESCE(t.estimate, 0) * COALESCE(t.progress, 0)) = 0) then 1 else (COALESCE(t.estimate, 0) * COALESCE(t.progress, 0)) end, 2) as factor " + 
      "from " + Task.class.getName() + " t " + 
      "left join t.category sc " + 
      "left join t.priority sp " + 
      "left join t.difficulty sd " + 
      "left join t.taskType st " + 
      "left join t.status ss " + 
      "left join t.project p " + 
      "left join t.owner o " + 
      "left join t.workgroup w " + 
      "left join p.status ps " + 
      "left join t.requirement r " + 
      "left join p.status sps " + 
      "left join t.iterationTasks it " + 
      "left join t.taskActivities a " + 
      "left join it.iteration i " + 
      "where sps.active = true and " + 
      "ss.done = false and " + 
      "(i.uuid <> :iterationUuid or it.uuid is null) " + filterHql + 
      "group by t.uuid, t.title, t.code, t.date, t.dueDate, " + 
      "t.startDate, t.endDate, t.constraintDate, t.closureDate, t.creationDate, " + 
      "sc.category, sp.priority, sd.difficulty, t.progress, st.type, " + 
      "ss.status, ss.color, p.rKey, p.name, ps.status, r.code, r.title, " + 
      "t.estimate, w.title, o.name, o.surname " + sortHql; 

    if (logger.isDebugEnabled()) { 
     logger.debug("Executing hql: " + hql); 
    } 

    Query query = hibernateTemplate.getSessionFactory().getCurrentSession().getSession(EntityMode.MAP).createQuery(hql); 
    for(String key: filterValues.keySet()) { 
     Object valueSet = filterValues.get(key); 

     if (logger.isDebugEnabled()) { 
      logger.debug("Setting query parameter for " + key); 
     } 

     if (valueSet instanceof java.util.Collection<?>) { 
      query.setParameterList(key, (Collection)filterValues.get(key)); 
     } else { 
      query.setParameter(key, filterValues.get(key)); 
     } 
    }  
    query.setString("iterationUuid", iteration.getUuid()); 
    query.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP); 

    if (logger.isDebugEnabled()) { 
     logger.debug("Query building complete."); 
     logger.debug("SQL: " + query.getQueryString()); 
    } 

    return query.list(); 
+0

¿Optimizado? ¿Puede explicar esto? – nalply

0

Estoy de acuerdo que se hizo el cálculo en el servidor de base de datos es su mejor opción en el caso particular que usted ha mencionado. HQL y JPAQL pueden manejar esas dos consultas:

1)

select w, sum(wl.hoursWorked) 
from Worker w, WorkLog wl 
where w.id = wl.workerId 
group by w 

o, si se asigna la asociación:

select w, sum(wl.hoursWorked) 
from Worker w join w.workLogs wl 
group by w 

ambos o que devuelven se lista en la que el Object [] s son trabajadores y largos. O también se puede utilizar consultas de instancias "dinámico" para envolver que hasta, por ejemplo:

select new WorkerTotal(select w, sum(wl.hoursWorked)) 
from Worker w join w.workLogs wl 
group by w 

o (dependiendo de la necesidad), probablemente incluso sólo:

select new WorkerTotal(select w.id, w.name, sum(wl.hoursWorked)) 
from Worker w join w.workLogs wl 
group by w.id, w.name 

WorkerTotal es sólo una clase normal. Debe tener constructor (es) correspondiente (s).

2)

select w, new Period(wl.start, wl.hoursWorked) 
from Worker w join w.workLogs wl 

esto le devolverá un resultado para cada fila en la tabla de registro de trabajo ... El bit new Period(...) se llama "instanciación dinámica" y se utiliza para envolver tuplas del resultado en objetos (consumo más fácil).

Para manipulación y uso general, recomiendo StatelessSession como señala Pascal.

0

Existen varias técnicas que pueden necesitar para ser usado en conjunción con otros para crear y manipular peticiones para grandes conjuntos de datos donde la memoria es una limitación:

  1. Uso setFetchSize (algún valor, quizá 100+) como el valor predeterminado (a través de JDBC) es 10. Esto es más sobre el rendimiento y es el mayor factor relacionado de los mismos. Se puede hacer en JPA usando queryHint disponible del proveedor (Hibernate, etc.). No (por algún motivo) parece ser un método JPA Query.setFetchSize(int).
  2. No intente utilizar todo el conjunto de resultados para 10K + registros. Se aplican varias estrategias: para GUI, use paginación o un marco que haga paginación. Considere Lucene o motores de búsqueda/indexación comercial (Endeca si la compañía tiene el dinero). Para enviar datos a alguna parte, transmítalos y vacíe el búfer cada N registros para limitar la cantidad de memoria utilizada. La secuencia se puede descargar a un archivo, red, etc. Recuerde que, debajo, JPA usa JDBC y JDBC mantiene el conjunto de resultados en el servidor, solo busca N-rows en un grupo de filas a la vez. Este desglose puede ser manipulado para facilitar el lavado de datos en grupos.
  3. Considere lo que es el caso de uso. Por lo general, una aplicación está tratando de responder preguntas. Cuando la respuesta es escardar a través de 10K + filas, entonces el diseño debe ser revisado. Una vez más, considere el uso de motores de indexación como Lucene, refinar las consultas, considere el uso BloomFilters como contiene cachés de verificación para encontrar aguja en un pajar sin ir a la base de datos, etc.
3

parece que se puede hacer esto con EclipseLink demasiado . Marque esta : http://wiki.eclipse.org/EclipseLink/Examples/JPA/Pagination:

Query query = em.createQuery... 
query.setHint(QueryHints.CURSOR, true) 
    .setHint(QueryHints.SCROLLABLE_CURSOR, true) 
ScrollableCursor scrl = (ScrollableCursor)q.getSingleResult(); 
Object o = null; 
while ((o = scrl.next()) != null) { ... } 
+0

setHint method shoing undefined. –

2

Este blog post también puede ayudar. Resume el enfoque con sesión sin estado y agrega algunos consejos adicionales, p. cómo transmitir resultados con JAX-RS.

Cuestiones relacionadas