Disons que j'ai une table avec des millions de lignes. En utilisant JPA, quelle est la bonne façon d'itérer une requête sur cette table, de sorte que je n'ai pas toute une liste en mémoire avec des millions d'objets?
Par exemple, je soupçonne que ce qui suit va exploser si la table est grande:
List<Model> models = entityManager().createQuery("from Model m", Model.class).getResultList();
for (Model model : models)
{
System.out.println(model.getId());
}
La pagination (mise en boucle et mise à jour manuelle setFirstResult()
/ setMaxResult()
) est-elle vraiment la meilleure solution?
Edit : le cas d'utilisation principal que je cible est une sorte de travail par lots. C'est bien si cela prend beaucoup de temps à fonctionner. Aucun client Web n'est impliqué; J'ai juste besoin de "faire quelque chose" pour chaque ligne, un (ou un petit N) à la fois. J'essaie juste d'éviter de tous les avoir en mémoire en même temps.
Réponses:
La page 537 de Java Persistence with Hibernate donne une solution utilisant
ScrollableResults
, mais hélas c'est uniquement pour Hibernate.Il semble donc que l'utilisation de
setFirstResult
/setMaxResults
et de l'itération manuelle soit vraiment nécessaire. Voici ma solution utilisant JPA:alors, utilisez-le comme ceci:
la source
size() == 100
sautera une requête supplémentaire qui renvoie une liste videJ'ai essayé les réponses présentées ici, mais JBoss 5.1 + MySQL Connector / J 5.1.15 + Hibernate 3.3.2 ne fonctionnait pas avec celles-ci. Nous venons de migrer de JBoss 4.x vers JBoss 5.1, donc nous nous en tenons pour le moment, et donc la dernière Hibernate que nous pouvons utiliser est la 3.3.2.
L'ajout de quelques paramètres supplémentaires a fait le travail, et un code comme celui-ci fonctionne sans OOME:
Les lignes cruciales sont les paramètres de requête entre createQuery et scroll. Sans eux, l'appel "scroll" essaie de tout charger en mémoire et ne se termine jamais ou s'exécute vers OutOfMemoryError.
la source
Vous ne pouvez pas vraiment faire cela dans JPA simple, mais Hibernate prend en charge les sessions sans état et les ensembles de résultats déroulants.
Nous traitons régulièrement des milliards de lignes avec son aide.
Voici un lien vers la documentation: http://docs.jboss.org/hibernate/core/3.3/reference/en/html/batch.html#batch-statelesssession
la source
Pour être honnête, je suggérerais de quitter JPA et de rester avec JDBC (mais certainement en utilisant une
JdbcTemplate
classe de support ou autre). JPA (et d'autres fournisseurs / spécifications ORM) n'est pas conçu pour fonctionner sur de nombreux objets dans une transaction car ils supposent que tout ce qui est chargé doit rester dans le cache de premier niveau (d'où la nécessité declear()
JPA).Je recommande également une solution de plus bas niveau car la surcharge de l'ORM (la réflexion n'est que la pointe d'un iceberg) pourrait être si importante, que l'itération sur la plaine
ResultSet
, même en utilisant un support léger comme mentionné,JdbcTemplate
sera beaucoup plus rapide.JPA n'est tout simplement pas conçu pour effectuer des opérations sur un grand nombre d'entités. Vous pourriez jouer avec
flush()
/clear()
pour éviterOutOfMemoryError
, mais réfléchissez encore une fois. Vous gagnez très peu à payer le prix d'une énorme consommation de ressources.la source
flush()
/clear()
. Le premier est IMHO pas conçu pour les besoins du traitement par lots, tout en utilisant la séquence de flush () / clear () sent comme l' abstraction qui fuit .Si vous utilisez EclipseLink I 'en utilisant cette méthode pour obtenir le résultat comme Iterable
close, méthode
la source
Cela dépend du type d'opération que vous devez effectuer. Pourquoi faites-vous une boucle sur plus d'un million de lignes? Mettez-vous à jour quelque chose en mode batch? Allez-vous afficher tous les enregistrements à un client? Calculez-vous des statistiques sur les entités récupérées?
Si vous allez afficher un million d'enregistrements au client, veuillez reconsidérer votre interface utilisateur. Dans ce cas, la solution appropriée consiste à paginer vos résultats et à utiliser
setFirstResult()
etsetMaxResult()
.Si vous avez lancé une mise à jour d'un grand nombre d'enregistrements, vous feriez mieux de garder la mise à jour simple et à utiliser
Query.executeUpdate()
. Si vous le souhaitez, vous pouvez exécuter la mise à jour en mode asynchrone à l'aide d'un Message-Driven Bean oa Work Manager.Si vous calculez des statistiques sur les entités récupérées, vous pouvez tirer parti des fonctions de regroupement définies par la spécification JPA.
Pour tout autre cas, soyez plus précis :)
la source
SELECT m.id FROM Model m
puis de l'itération sur un List <Integer>.Il n'y a pas de «bon» quoi faire, ce n'est pas ce que JPA ou JDO ou tout autre ORM est censé faire, JDBC direct sera votre meilleure alternative, car vous pouvez le configurer pour ramener un petit nombre de lignes à un temps et les vider au fur et à mesure qu'ils sont utilisés, c'est pourquoi les curseurs côté serveur existent.
Les outils ORM ne sont pas conçus pour le traitement en masse, ils sont conçus pour vous permettre de manipuler des objets et d'essayer de rendre le SGBDR dans lequel les données sont stockées aussi transparent que possible, la plupart échouent au niveau de la partie transparente au moins dans une certaine mesure. À cette échelle, il n'y a aucun moyen de traiter des centaines de milliers de lignes (objets), encore moins des millions avec n'importe quel ORM et de l'exécuter dans un laps de temps raisonnable en raison de la surcharge d'instanciation d'objet, pure et simple.
Utilisez l'outil approprié. Les procédures simples JDBC et stockées ont définitivement leur place en 2011, en particulier pour ce qu'elles font mieux par rapport à ces frameworks ORM.
Tirer un million de tout, même dans un simple,
List<Integer>
ne sera pas très efficace quelle que soit la façon dont vous le faites. La bonne façon de faire ce que vous demandez est un simpleSELECT id FROM table
, réglé surSERVER SIDE
(dépend du fournisseur) et le curseurFORWARD_ONLY READ-ONLY
et itérer dessus.Si vous tirez vraiment des millions d'identifiants à traiter en appelant un serveur Web avec chacun d'eux, vous devrez également effectuer un traitement simultané pour que cela s'exécute dans un laps de temps raisonnable. Tirer avec un curseur JDBC et en placer quelques-uns à la fois dans un ConcurrentLinkedQueue et avoir un petit pool de threads (# CPU / Cores + 1) les extraire et les traiter est le seul moyen de terminer votre tâche sur une machine avec tout " "normal" de RAM, étant donné que vous manquez déjà de mémoire.
Voyez également cette réponse .
la source
Vous pouvez utiliser un autre "truc". Chargez uniquement la collection d'identifiants des entités qui vous intéressent. Disons que l'identifiant est de type long = 8 octets, alors 10 ^ 6 une liste de ces identifiants fait environ 8 Mo. S'il s'agit d'un processus par lots (une instance à la fois), alors c'est supportable. Ensuite, répétez simplement et faites le travail.
Une autre remarque - vous devriez de toute façon le faire par morceaux - surtout si vous modifiez des enregistrements, sinon le segment de restauration dans la base de données augmentera.
Quand il s'agit de définir la stratégie firstResult / maxRows - ce sera TRÈS TRÈS lent pour les résultats loin du sommet.
Tenez également compte du fait que la base de données fonctionne probablement dans un isolement de lecture validée , afin d'éviter les lectures fantômes, chargez les identificateurs, puis chargez les entités une par une (ou 10 par 10 ou autre).
la source
J'ai été surpris de voir que l'utilisation de procédures stockées n'était pas plus importante dans les réponses ici. Dans le passé, lorsque je devais faire quelque chose comme ça, je crée une procédure stockée qui traite les données par petits morceaux, puis dort un peu, puis continue. La raison de la mise en veille est de ne pas submerger la base de données qui est vraisemblablement également utilisée pour des types de requêtes plus en temps réel, comme la connexion à un site Web. S'il n'y a personne d'autre qui utilise la base de données, vous pouvez laisser de côté le sommeil. Si vous devez vous assurer de traiter chaque enregistrement une et une seule fois, vous devrez créer une table (ou un champ) supplémentaire pour stocker les enregistrements que vous avez traités afin d'être résilient lors des redémarrages.
Les économies de performances ici sont significatives, peut-être des ordres de grandeur plus rapides que tout ce que vous pourriez faire dans JPA / Hibernate / AppServer, et votre serveur de base de données aura très probablement son propre type de mécanisme de curseur côté serveur pour traiter efficacement de grands ensembles de résultats. Les économies de performances proviennent du fait de ne pas avoir à expédier les données du serveur de base de données au serveur d'applications, où vous traitez les données, puis les renvoyez.
Il y a des inconvénients importants à utiliser des procédures stockées qui peuvent complètement exclure cela pour vous, mais si vous avez cette compétence dans votre boîte à outils personnelle et que vous pouvez l'utiliser dans ce genre de situation, vous pouvez éliminer ce genre de choses assez rapidement. .
la source
Pour développer la réponse de @Tomasz Nurkiewicz. Vous avez accès au
DataSource
qui à son tour peut vous fournir une connexionDans votre code, vous avez
Cela vous permettra de contourner JPA pour certaines opérations par lots volumineuses spécifiques telles que l'importation / exportation, mais vous avez toujours accès au gestionnaire d'entités pour d'autres opérations JPA si vous en avez besoin.
la source
Utilisez
Pagination
Concept pour récupérer le résultatla source
Je me suis posé cette question moi-même. Cela semble important:
J'ai écrit un Iterator pour faciliter l'échange des deux approches (findAll vs findEntries).
Je vous recommande d'essayer les deux.
J'ai fini par ne pas utiliser mon itérateur de bloc (donc ce n'est peut-être pas si testé). Au fait, vous aurez besoin de collections google si vous souhaitez l'utiliser.
la source
Avec la mise en veille prolongée, il existe 4 façons différentes de réaliser ce que vous voulez. Chacun a des compromis, des limites et des conséquences en matière de conception. Je suggère d'explorer chacun et de décider lequel convient à votre situation.
la source
Voici un exemple JPA simple et direct (dans Kotlin) qui montre comment vous pouvez paginer sur un jeu de résultats arbitrairement grand, en lisant des morceaux de 100 éléments à la fois, sans utiliser de curseur (chaque curseur consomme des ressources sur la base de données). Il utilise la pagination du jeu de clés.
Voir https://use-the-index-luke.com/no-offset pour le concept de pagination du jeu de clés et https://www.citusdata.com/blog/2016/03/30/five-ways-to- paginer / pour une comparaison des différentes manières de paginer avec leurs inconvénients.
la source
Un exemple avec JPA et NativeQuery récupérant à chaque fois les éléments de taille à l'aide de décalages
la source