JPA: quel est le modèle approprié pour itérer sur de grands ensembles de résultats?

114

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.

George Armhold
la source
Quelle base de données et quel pilote JDBC utilisez-vous?

Réponses:

55

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/ setMaxResultset de l'itération manuelle soit vraiment nécessaire. Voici ma solution utilisant JPA:

private List<Model> getAllModelsIterable(int offset, int max)
{
    return entityManager.createQuery("from Model m", Model.class).setFirstResult(offset).setMaxResults(max).getResultList();
}

alors, utilisez-le comme ceci:

private void iterateAll()
{
    int offset = 0;

    List<Model> models;
    while ((models = Model.getAllModelsIterable(offset, 100)).size() > 0)
    {
        entityManager.getTransaction().begin();
        for (Model model : models)
        {
            log.info("do something with model: " + model.getId());
        }

        entityManager.flush();
        entityManager.clear();
        em.getTransaction().commit();
        offset += models.size();
    }
}
George Armhold
la source
33
Je pense que l'exemple n'est pas sûr s'il y a de nouveaux inserts pendant le processus par lots. L'utilisateur doit commander en fonction d'une colonne où il est sûr que les données nouvellement insérées seront à la fin de la liste de résultats.
Balazs Zsoldos
lorsque la page actuelle est la dernière page et contient moins de 100 éléments, la vérification size() == 100sautera une requête supplémentaire qui renvoie une liste vide
cdalxndr
38

J'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:

        StatelessSession session = ((Session) entityManager.getDelegate()).getSessionFactory().openStatelessSession();

        Query query = session
                .createQuery("SELECT a FROM Address a WHERE .... ORDER BY a.id");
        query.setFetchSize(Integer.valueOf(1000));
        query.setReadOnly(true);
        query.setLockMode("a", LockMode.NONE);
        ScrollableResults results = query.scroll(ScrollMode.FORWARD_ONLY);
        while (results.next()) {
            Address addr = (Address) results.get(0);
            // Do stuff
        }
        results.close();
        session.close();

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.

Zds
la source
2
Salut Zds, votre cas d'utilisation de l'analyse de millions de lignes est certainement courant pour moi, et MERCI d'avoir publié le code final. Dans mon cas, je pousse des enregistrements dans Solr, pour les indexer pour une recherche en texte intégral. Et, en raison de règles commerciales que je ne vais pas aborder, je dois passer par Hibernate, plutôt qu'en utilisant simplement les modules intégrés de JDBC ou de Solr.
Mark Bennett
Heureux d'aider :-). Nous avons également affaire à de grands ensembles de données, dans ce cas permettant à l'utilisateur d'interroger tous les noms de rue dans la même ville / comté, ou parfois même état, donc la création d'indices nécessite la lecture de beaucoup de données.
Zds
Apparaît avec MySQL, vous devez vraiment passer par tous ces obstacles: stackoverflow.com/a/20900045/32453 (d'autres bases de données pourraient être moins strictes, j'imagine ...)
rogerdpack
32

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

Cyberax
la source
17
Merci. Il est bon de savoir que quelqu'un fait des milliards de lignes via Hibernate. Certaines personnes ici prétendent que c'est impossible. :-)
George Armhold
2
Est-il possible d'ajouter un exemple ici également? Je suppose que c'est similaire à l'exemple de Zds?
rogerdpack
19

Pour être honnête, je suggérerais de quitter JPA et de rester avec JDBC (mais certainement en utilisant une JdbcTemplateclasse 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é de clear()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é, JdbcTemplatesera 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 éviter OutOfMemoryError, mais réfléchissez encore une fois. Vous gagnez très peu à payer le prix d'une énorme consommation de ressources.

Tomasz Nurkiewicz
la source
L'avantage de JPA n'est pas seulement d'être indépendant de la base de données, mais la possibilité de ne même pas utiliser une base de données traditionnelle (NoSQL). Ce n'est pas trop difficile de faire rincer / effacer de temps en temps et les opérations par lots sont généralement effectuées rarement.
Adam Gent
1
Salut Thomasz. J'ai de nombreuses raisons de me plaindre de JPA / Hibernate, mais respectueusement, je doute vraiment qu'ils ne soient "pas conçus pour fonctionner sur de nombreux objets". Je soupçonne que j'ai juste besoin d'apprendre le modèle approprié pour ce cas d'utilisation.
George Armhold
4
Eh bien, je ne peux penser qu'à deux modèles: les paginations (mentionnées plusieurs fois) et 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 .
Tomasz Nurkiewicz
Oui, c'était une combinaison de pagination et de flush / clear comme vous l'avez mentionné. Merci!
George Armhold
7

Si vous utilisez EclipseLink I 'en utilisant cette méthode pour obtenir le résultat comme Iterable

private static <T> Iterable<T> getResult(TypedQuery<T> query)
{
  //eclipseLink
  if(query instanceof JpaQuery) {
    JpaQuery<T> jQuery = (JpaQuery<T>) query;
    jQuery.setHint(QueryHints.RESULT_SET_TYPE, ResultSetType.ForwardOnly)
       .setHint(QueryHints.SCROLLABLE_CURSOR, true);

    final Cursor cursor = jQuery.getResultCursor();
    return new Iterable<T>()
    {     
      @SuppressWarnings("unchecked")
      @Override
      public Iterator<T> iterator()
      {
        return cursor;
      }
    }; 
   }
  return query.getResultList();  
}  

close, méthode

static void closeCursor(Iterable<?> list)
{
  if (list.iterator() instanceof Cursor)
    {
      ((Cursor) list.iterator()).close();
    }
}
user2008477
la source
6
Bel objet jQuery
usr-local-ΕΨΗΕΛΩΝ
J'ai essayé votre code mais j'obtiens toujours MOO - il semble que tous les objets T (et tous les objets de table joints référencés à partir de T) ne soient jamais GC. Le profilage montre qu'ils sont référencés à partir de "table" dans org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork avec org.eclipse.persistence.internal.identitymaps.CacheKey. J'ai regardé dans le cache et mes paramètres sont tous par défaut (Désactiver sélectif, Faible avec Soft Subcache, Taille du cache 100, Drop Invalidate). Je vais examiner la désactivation des sessions et voir si cela aide. BTW je itère simplement sur le curseur de retour en utilisant "for (T o: results)".
Edi Bice
Badum tssssssss
dctremblay
5

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()et setMaxResult().

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

frm
la source
Tout simplement, j'ai besoin de faire quelque chose "pour chaque" ligne. C'est sûrement un cas d'utilisation courant. Dans le cas spécifique sur lequel je travaille actuellement, je dois interroger un service Web externe qui est totalement en dehors de ma base de données, en utilisant un identifiant (le PK) de chaque ligne. Les résultats ne sont affichés sur aucun navigateur Web client, il n'y a donc pas d'interface utilisateur à proprement parler. C'est un travail par lots, en d'autres termes.
George Armhold
Si vous "avez besoin" de l'ID d'impression pour chaque ligne, il n'y a pas d'autre moyen d'obtenir chaque ligne, d'obtenir l'ID et d'imprimer. La meilleure solution dépend de ce que vous devez faire.
Dainius
@Caffeine Coma, si vous n'avez besoin que de l'identifiant de chaque ligne, la plus grande amélioration proviendrait probablement de la récupération de cette colonne, car SELECT m.id FROM Model m puis de l'itération sur un List <Integer>.
Jörn Horstmann le
1
@ Jörn Horstmann - s'il y a des millions de lignes, cela aura-t-il vraiment de l'importance? Mon point est qu'une ArrayList avec des millions d'objets (même petits) ne va pas être bon pour le tas JVM.
George Armhold
@Dainius: ma question est vraiment: "comment puis-je parcourir chaque ligne, sans avoir toute l'ArrayList en mémoire?" En d'autres termes, j'aimerais une interface pour tirer N à la fois, où N est nettement inférieur à 1 million. :-)
George Armhold
5

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 simple SELECT id FROM table, réglé sur SERVER SIDE(dépend du fournisseur) et le curseur FORWARD_ONLY READ-ONLYet 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 .

Communauté
la source
1
Vous dites donc qu'aucune entreprise n'a jamais besoin de visiter chaque ligne de sa table d'utilisateurs? Leurs programmeurs jettent juste Hibernate par la fenêtre quand vient le temps de faire ça? « Il n'y a aucun moyen de processus des centaines de milliers de lignes » - dans ma question je l' ai dit setFirstResult / setMaxResult, si clairement il est un moyen. Je demande s'il y en a un meilleur.
George Armhold
"Tirer un million de n'importe quoi, même dans une simple List <Integer> ne sera pas très efficace quelle que soit la façon dont vous le faites." C'est exactement ce que je veux dire. Je demande comment ne pas créer la liste géante, mais plutôt parcourir un ensemble de résultats.
George Armhold
Utilisez une instruction de sélection JDBC simple avec un FORWARD_ONLY READ_ONLY avec un curseur SERVER_SIDE comme je l'ai suggéré dans ma réponse. Comment faire en sorte que JDBC utilise un curseur SERVER_SIDE dépend du pilote de base de données.
1
Je suis entièrement d'accord avec la réponse. La meilleure solution dépend du problème. Si le problème est de charger facilement quelques entités, JPA est bon. Si le problème consiste à utiliser efficacement d'énormes quantités de données, le JDBC direct est préférable.
extraneon
4
L'analyse de millions d'enregistrements est courante pour un certain nombre de raisons, par exemple pour les indexer dans un moteur de recherche. Et bien que je convienne que JDBC est normalement une route plus directe, vous entrez parfois dans un projet qui a déjà une logique métier très complexe regroupée dans une couche Hibernate. Si vous le contournez et passez à JDBC, vous contournez la logique métier, qui est parfois non triviale à réimplémenter et à maintenir. Lorsque les gens posent des questions sur des cas d'utilisation atypiques, ils savent souvent que c'est un peu étrange, mais peuvent hériter de quelque chose plutôt que de construire à partir de zéro, et peut-être ne peuvent pas divulguer les détails.
Mark Bennett
4

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

Marcin Cinik
la source
Salut @Marcin, pouvez-vous ou quelqu'un d'autre fournir un lien vers un exemple de code appliquant cette approche par étapes et id-first, de préférence en utilisant des flux Java8?
krevelen le
2

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

Danger
la source
1
-2 votes négatifs - est-ce que le prochain vote défavorable pourrait défendre votre vote défavorable?
Danger
1
J'ai pensé la même chose en lisant ces derniers. La question indique un travail par lots à volume élevé sans interface utilisateur. En supposant que vous n'avez pas besoin de ressources spécifiques au serveur d'applications, pourquoi utiliser un serveur d'applications? La procédure stockée serait beaucoup plus efficace.
jdessey
@jdessey Selon la situation, disons que nous avons une fonction d'importation où, lors de l'importation, il devrait faire quelque chose avec une autre partie du système, par exemple ajouter des lignes à une autre table basée sur certaines règles métier qui ont déjà été codées comme un EJB. L'exécution dans un serveur d'applications aurait alors plus de sens, à moins que vous ne puissiez exécuter l'EJB en mode intégré.
Archimedes Trajano
1

Pour développer la réponse de @Tomasz Nurkiewicz. Vous avez accès au DataSourcequi à son tour peut vous fournir une connexion

@Resource(name = "myDataSource",
    lookup = "java:comp/DefaultDataSource")
private DataSource myDataSource;

Dans votre code, vous avez

try (Connection connection = myDataSource.getConnection()) {
    // raw jdbc operations
}

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.

Archimède Trajano
la source
0

Utilisez PaginationConcept pour récupérer le résultat

Programmeur mort
la source
4
La pagination est très bonne pour les GUI. Mais pour traiter d'énormes quantités de données, le ScrollableResultSet a été inventé il y a longtemps. Ce n'est tout simplement pas dans JPA.
extraneon
0

Je me suis posé cette question moi-même. Cela semble important:

  • la taille de votre ensemble de données (lignes)
  • quelle implémentation JPA vous utilisez
  • quel type de traitement vous effectuez pour chaque ligne.

J'ai écrit un Iterator pour faciliter l'échange des deux approches (findAll vs findEntries).

Je vous recommande d'essayer les deux.

Long count = entityManager().createQuery("select count(o) from Model o", Long.class).getSingleResult();
ChunkIterator<Model> it1 = new ChunkIterator<Model>(count, 2) {

    @Override
    public Iterator<Model> getChunk(long index, long chunkSize) {
        //Do your setFirst and setMax here and return an iterator.
    }

};

Iterator<Model> it2 = List<Model> models = entityManager().createQuery("from Model m", Model.class).getResultList().iterator();


public static abstract class ChunkIterator<T> 
    extends AbstractIterator<T> implements Iterable<T>{
    private Iterator<T> chunk;
    private Long count;
    private long index = 0;
    private long chunkSize = 100;

    public ChunkIterator(Long count, long chunkSize) {
        super();
        this.count = count;
        this.chunkSize = chunkSize;
    }

    public abstract Iterator<T> getChunk(long index, long chunkSize);

    @Override
    public Iterator<T> iterator() {
        return this;
    }

    @Override
    protected T computeNext() {
        if (count == 0) return endOfData();
        if (chunk != null && chunk.hasNext() == false && index >= count) 
            return endOfData();
        if (chunk == null || chunk.hasNext() == false) {
            chunk = getChunk(index, chunkSize);
            index += chunkSize;
        }
        if (chunk == null || chunk.hasNext() == false) 
            return endOfData();
        return chunk.next();
    }

}

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.

Adam Gent
la source
En ce qui concerne "le type de traitement que vous faites pour chaque ligne" - si le nombre de lignes est en millions, je soupçonne que même un simple objet avec seulement une colonne id va poser des problèmes. J'ai aussi pensé à écrire mon propre Iterator qui encapsulait setFirstResult / setMaxResult, mais j'ai pensé que cela devait être un problème courant (et, espérons-le, résolu!).
George Armhold
@Caffeine Coma J'ai publié mon Iterator, vous pourriez probablement faire un peu plus de JPA en vous y adaptant. Dites-moi si cela aide. J'ai fini par ne pas utiliser (j'ai fait un findAll).
Adam Gent
0

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.

  1. Utiliser une session sans état avec scroll ()
  2. Utilisez session.clear () après chaque itération. Lorsque d'autres entités doivent être jointes, chargez-les dans une session distincte. en fait, la première session émule la session sans état, mais conserve toutes les fonctionnalités d'une session avec état, jusqu'à ce que les objets soient détachés.
  3. Utilisez iterate () ou list () mais obtenez uniquement les identifiants dans la première requête, puis dans une session séparée à chaque itération, faites session.load et fermez la session à la fin de l'itération.
  4. Utilisez Query.iterate () avec EntityManager.detach () aka Session.evict ();
Larry Chu
la source
0

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.

/*
create table my_table(
  id int primary key, -- index will be created
  my_column varchar
)
*/

fun keysetPaginationExample() {
    var lastId = Integer.MIN_VALUE
    do {

        val someItems =
        myRepository.findTop100ByMyTableIdAfterOrderByMyTableId(lastId)

        if (someItems.isEmpty()) break

        lastId = someItems.last().myTableId

        for (item in someItems) {
          process(item)
        }

    } while (true)
}
Elifarley
la source
0

Un exemple avec JPA et NativeQuery récupérant à chaque fois les éléments de taille à l'aide de décalages

public List<X> getXByFetching(int fetchSize) {
        int totalX = getTotalRows(Entity);
        List<X> result = new ArrayList<>();
        for (int offset = 0; offset < totalX; offset = offset + fetchSize) {
            EntityManager entityManager = getEntityManager();
            String sql = getSqlSelect(Entity) + " OFFSET " + offset + " ROWS";
            Query query = entityManager.createNativeQuery(sql, X.class);
            query.setMaxResults(fetchSize);
            result.addAll(query.getResultList());
            entityManager.flush();
            entityManager.clear();
        return result;
    }
harryssuperman
la source