Digamos que tengo una tabla con millones de filas. Usando JPA, ¿cuál es la forma correcta de iterar sobre una consulta en esa tabla, de modo que no tenga toda una lista en memoria con millones de objetos?
Por ejemplo, sospecho que lo siguiente explotará si la mesa es grande:
List<Model> models = entityManager().createQuery("from Model m", Model.class).getResultList();
for (Model model : models)
{
System.out.println(model.getId());
}
¿Es la paginación (bucle y actualización manual setFirstResult()
/setMaxResult()
) realmente la mejor solución?
Editar : el caso de uso principal al que me dirijo es una especie de trabajo por lotes. Está bien si tarda mucho en ejecutarse. No hay ningún cliente web involucrado; Solo necesito "hacer algo" para cada fila, una (o una pequeña N) a la vez. Solo intento evitar tenerlos todos en la memoria al mismo tiempo.
Respuestas:
La página 537 de Java Persistence with Hibernate ofrece una solución usando
ScrollableResults
, pero lamentablemente es solo para Hibernate.Entonces parece que usar
setFirstResult
/setMaxResults
y la iteración manual realmente es necesario. Aquí está mi solución usando JPA:luego, úsalo así:
fuente
size() == 100
se saltará una consulta adicional que devuelve una lista vacíaProbé las respuestas presentadas aquí, pero JBoss 5.1 + MySQL Connector / J 5.1.15 + Hibernate 3.3.2 no funcionó con ellas. Acabamos de migrar de JBoss 4.xa JBoss 5.1, así que nos hemos quedado con él por ahora y, por lo tanto, la última versión de Hibernate que podemos usar es la 3.3.2.
Agregar un par de parámetros adicionales hizo el trabajo, y un código como este se ejecuta sin OOMEs:
Las líneas cruciales son los parámetros de consulta entre createQuery y scroll. Sin ellos, la llamada "scroll" intenta cargar todo en la memoria y nunca termina o se ejecuta en OutOfMemoryError.
fuente
Realmente no puede hacer esto en JPA directo, sin embargo, Hibernate tiene soporte para sesiones sin estado y conjuntos de resultados desplazables.
Procesamos rutinariamente miles de millones de filas con su ayuda.
Aquí hay un enlace a la documentación: http://docs.jboss.org/hibernate/core/3.3/reference/en/html/batch.html#batch-statelesssession
fuente
Para ser honesto, sugeriría dejar JPA y seguir con JDBC (pero ciertamente usando
JdbcTemplate
clases de soporte o similares). JPA (y otros proveedores / especificaciones de ORM) no está diseñado para operar en muchos objetos dentro de una transacción, ya que asumen que todo lo cargado debe permanecer en la caché de primer nivel (de ahí la necesidad declear()
JPA).También recomiendo una solución de nivel más bajo porque la sobrecarga de ORM (la reflexión es solo la punta de un iceberg) podría ser tan significativa, que iterar sobre el plano
ResultSet
, incluso usar algún soporte liviano como el mencionadoJdbcTemplate
, será mucho más rápido.JPA simplemente no está diseñado para realizar operaciones en una gran cantidad de entidades. Puede jugar con
flush()
/clear()
para evitarOutOfMemoryError
, pero considere esto una vez más. Ganas muy poco pagando el precio de un enorme consumo de recursos.fuente
flush()
/clear()
. En mi humilde opinión, el primero no está diseñado para fines de procesamiento por lotes, mientras que el uso de la secuencia de flush () / clear () huele a abstracción con fugas .Si usa EclipseLink I 'usando este método para obtener el resultado como Iterable
método de cierre
fuente
Depende del tipo de operación que tenga que realizar. ¿Por qué recorre más de un millón de filas? ¿Está actualizando algo en modo por lotes? ¿Vas a mostrar todos los registros a un cliente? ¿Está calculando algunas estadísticas sobre las entidades recuperadas?
Si va a mostrar un millón de registros al cliente, reconsidere su interfaz de usuario. En este caso, la solución adecuada es paginar sus resultados y usar
setFirstResult()
ysetMaxResult()
.Si ha lanzado una actualización de una gran cantidad de registros, será mejor que mantenga la actualización simple y útil
Query.executeUpdate()
. Opcionalmente, puede ejecutar la actualización en modo asíncrono utilizando un Bean o Administrador de trabajo basado en mensajes.Si está calculando algunas estadísticas sobre las entidades recuperadas, puede aprovechar las funciones de agrupación definidas por la especificación JPA.
Para cualquier otro caso, sea más específico :)
fuente
SELECT m.id FROM Model m
y luego iterar sobre una List <Integer>.No hay "correcto" qué hacer esto, esto no es lo que JPA o JDO o cualquier otro ORM está destinado a hacer, JDBC directo será su mejor alternativa, ya que puede configurarlo para recuperar una pequeña cantidad de filas en un tiempo y vaciarlos a medida que se utilizan, es por eso que existen cursores del lado del servidor.
Las herramientas ORM no están diseñadas para procesamiento masivo, están diseñadas para permitirle manipular objetos e intentar hacer que el RDBMS en el que se almacenan los datos sea lo más transparente posible, la mayoría falla en la parte transparente al menos hasta cierto punto. A esta escala, no hay forma de procesar cientos de miles de filas (Objetos), mucho menos millones con cualquier ORM y hacer que se ejecute en un tiempo razonable debido a la sobrecarga de creación de instancias de objetos, simple y llanamente.
Utilice la herramienta adecuada. El JDBC directo y los procedimientos almacenados definitivamente tienen un lugar en 2011, especialmente en lo que son mejores en hacer frente a estos marcos ORM.
Extraer un millón de cualquier cosa, incluso en un simple,
List<Integer>
no será muy eficiente, independientemente de cómo lo hagas. La forma correcta de hacer lo que está pidiendo es simpleSELECT id FROM table
, establecido enSERVER SIDE
(dependiente del proveedor) y el cursor enFORWARD_ONLY READ-ONLY
y iterar sobre eso.Si realmente está extrayendo millones de identificaciones para procesar llamando a algún servidor web con cada una, también tendrá que realizar un procesamiento simultáneo para que esto se ejecute en un período de tiempo razonable. Tirando con un cursor JDBC y colocando algunos de ellos a la vez en una ConcurrentLinkedQueue y tener un pequeño grupo de subprocesos (# CPU / Cores + 1) extraerlos y procesarlos es la única forma de completar su tarea en una máquina con cualquier " "cantidad normal" de RAM, dado que ya se está quedando sin memoria.
Vea esta respuesta también.
fuente
Puedes usar otro "truco". Cargue solo la colección de identificadores de las entidades que le interesan. Digamos que el identificador es de tipo long = 8bytes, luego 10 ^ 6 una lista de tales identificadores hace alrededor de 8Mb. Si es un proceso por lotes (una instancia a la vez), entonces es soportable. Luego, repita y haga el trabajo.
Otro comentario: de todos modos, debe hacer esto en trozos, especialmente si modifica registros, de lo contrario, el segmento de retroceso en la base de datos crecerá.
Cuando se trata de establecer la estrategia firstResult / maxRows, será MUY MUY lento para los resultados lejos de la cima.
También tenga en cuenta que la base de datos probablemente esté operando en aislamiento de lectura confirmada , por lo que para evitar las lecturas fantasma cargue identificadores y luego cargue las entidades una por una (o 10 por 10 o lo que sea).
fuente
Me sorprendió ver que el uso de procedimientos almacenados no era más prominente en las respuestas aquí. En el pasado, cuando tenía que hacer algo como esto, creaba un procedimiento almacenado que procesa datos en pequeños fragmentos, luego duerme un poco y luego continúa. El motivo de la suspensión es no abrumar la base de datos que presumiblemente también se utiliza para tipos de consultas más en tiempo real, como estar conectado a un sitio web. Si no hay nadie más usando la base de datos, puede omitir la suspensión. Si necesita asegurarse de procesar cada registro una vez y solo una vez, deberá crear una tabla (o campo) adicional para almacenar los registros que ha procesado a fin de ser resistente en los reinicios.
Los ahorros de rendimiento aquí son significativos, posiblemente órdenes de magnitud más rápidos que cualquier cosa que pueda hacer en JPA / Hibernate / AppServer land, y su servidor de base de datos probablemente tendrá su propio tipo de mecanismo de cursor del lado del servidor para procesar grandes conjuntos de resultados de manera eficiente. Los ahorros de rendimiento provienen de no tener que enviar los datos desde el servidor de la base de datos al servidor de aplicaciones, donde procesa los datos y luego los devuelve.
Existen algunas desventajas importantes en el uso de procedimientos almacenados que pueden descartarlo por completo, pero si tiene esa habilidad en su caja de herramientas personal y puede usarla en este tipo de situación, puede eliminar este tipo de cosas con bastante rapidez. .
fuente
Para ampliar la respuesta de @Tomasz Nurkiewicz. Tienes acceso al
DataSource
que a su vez puede proporcionarte una conexiónEn tu código tienes
Esto le permitirá omitir JPA para algunas operaciones por lotes grandes específicas como la importación / exportación, sin embargo, aún tiene acceso al administrador de la entidad para otras operaciones JPA si lo necesita.
fuente
Utilice
Pagination
Concept para recuperar el resultadofuente
Yo mismo me lo he preguntado. Parece importar:
He escrito un iterador para facilitar el intercambio de ambos enfoques (findAll vs findEntries).
Te recomiendo que pruebes ambos.
Terminé sin usar mi iterador de fragmentos (por lo que podría no ser tan probado). Por cierto, necesitarás colecciones de Google si quieres usarlo.
fuente
Con hibernate hay 4 formas diferentes de lograr lo que quieres. Cada uno tiene compensaciones, limitaciones y consecuencias de diseño. Sugiero explorar cada uno y decidir cuál es el adecuado para su situación.
fuente
Aquí hay un ejemplo de JPA simple y directo (en Kotlin) que muestra cómo puede paginar sobre un conjunto de resultados arbitrariamente grande, leyendo fragmentos de 100 elementos a la vez, sin usar un cursor (cada cursor consume recursos en la base de datos). Utiliza la paginación del conjunto de claves.
Consulte https://use-the-index-luke.com/no-offset para conocer el concepto de paginación del conjunto de claves y https://www.citusdata.com/blog/2016/03/30/five-ways-to- paginate / para una comparación de diferentes formas de paginar junto con sus inconvenientes.
fuente
Un ejemplo con JPA y NativeQuery recuperando cada vez los elementos de tamaño usando compensaciones
fuente