JPA: Was ist das richtige Muster für die Iteration über große Ergebnismengen?


114

Angenommen, ich habe eine Tabelle mit Millionen von Zeilen. Was ist bei Verwendung von JPA der richtige Weg, um eine Abfrage für diese Tabelle zu durchlaufen, sodass ich nicht alle eine speicherinterne Liste mit Millionen von Objekten habe?

Ich vermute zum Beispiel, dass Folgendes explodiert, wenn der Tisch groß ist:

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

for (Model model : models)
{
     System.out.println(model.getId());
}

Ist Paginierung (Schleifen und manuelles Aktualisieren setFirstResult()/ setMaxResult()) wirklich die beste Lösung?

Bearbeiten : Der primäre Anwendungsfall, auf den ich abziele, ist eine Art Stapeljob. Es ist in Ordnung, wenn die Ausführung lange dauert. Es ist kein Webclient beteiligt. Ich muss nur für jede Zeile "etwas" tun, eine (oder ein kleines N) nach der anderen. Ich versuche nur zu vermeiden, dass sie alle gleichzeitig im Gedächtnis bleiben.


Welche Datenbank und welchen JDBC-Treiber verwenden Sie?

Antworten:


55

Seite 537 von Java Persistence with Hibernate bietet eine Lösung mit ScrollableResults, aber leider nur für Hibernate.

Es scheint also, dass die Verwendung von setFirstResult/ setMaxResultsund manueller Iteration wirklich notwendig ist. Hier ist meine Lösung mit JPA:

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

Verwenden Sie es dann folgendermaßen:

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();
    }
}

33
Ich denke, das Beispiel ist nicht sicher, wenn es während des Stapelprozesses neue Einsätze gibt. Der Benutzer muss anhand einer Spalte bestellen, in der sicher ist, dass neu eingefügte Daten am Ende der Ergebnisliste stehen.
Balazs Zsoldos

Wenn die aktuelle Seite die letzte Seite ist und weniger als 100 Elemente überprüft size() == 100werden, wird stattdessen eine zusätzliche Abfrage
übersprungen

37

Ich habe die hier vorgestellten Antworten ausprobiert, aber JBoss 5.1 + MySQL Connector / J 5.1.15 + Hibernate 3.3.2 hat mit diesen nicht funktioniert. Wir sind gerade von JBoss 4.x auf JBoss 5.1 migriert, haben uns also vorerst daran gehalten, und daher ist der neueste Ruhezustand, den wir verwenden können, 3.3.2.

Das Hinzufügen einiger zusätzlicher Parameter hat den Job erledigt, und Code wie dieser wird ohne OOMEs ausgeführt:

        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();

Die entscheidenden Zeilen sind die Abfrageparameter zwischen createQuery und scroll. Ohne sie versucht der "scroll" -Aufruf, alles in den Speicher zu laden und wird entweder nie beendet oder in OutOfMemoryError ausgeführt.


2
Hallo Zds, Ihr Anwendungsfall, Millionen von Zeilen zu scannen, ist für mich sicherlich üblich, und DANKE, dass Sie den endgültigen Code veröffentlicht haben. In meinem Fall schiebe ich Datensätze in Solr, um sie für die Volltextsuche zu indizieren. Und aufgrund von Geschäftsregeln, auf die ich nicht eingehen werde, muss ich über den Ruhezustand gehen, anstatt nur JDBC oder die integrierten Module von Solr zu verwenden.
Mark Bennett

Freue mich zu helfen :-). Wir haben es auch mit großen Datenmengen zu tun. In diesem Fall können Benutzer alle Straßennamen innerhalb derselben Stadt / Grafschaft oder manchmal sogar Bundesland abfragen. Für die Erstellung von Indizes müssen daher viele Daten gelesen werden.
Zds

Erscheint mit MySQL, müssen Sie wirklich alle diese Reifen durchlaufen : stackoverflow.com/a/20900045/32453 (andere DBs könnten weniger streng sein, würde ich mir vorstellen ...)
Rogerdpack

32

In Straight JPA ist dies nicht wirklich möglich. Hibernate unterstützt jedoch zustandslose Sitzungen und scrollbare Ergebnismengen.

Mit seiner Hilfe verarbeiten wir routinemäßig Milliarden von Zeilen.

Hier ist ein Link zur Dokumentation: http://docs.jboss.org/hibernate/core/3.3/reference/en/html/batch.html#batch-statelesssession


17
Vielen Dank. Gut zu wissen, dass jemand Milliarden von Zeilen über den Ruhezustand erstellt. Einige Leute hier behaupten, es sei unmöglich. :-)
George Armhold

2
Kann man hier auch ein Beispiel hinzufügen? Ich nehme an, es ähnelt dem Beispiel von Zds?
Rogerdpack

19

Um ehrlich zu sein, würde ich vorschlagen, JPA zu verlassen und bei JDBC zu bleiben (aber sicherlich JdbcTemplateSupport-Klassen oder ähnliches zu verwenden). JPA (und andere ORM-Anbieter / Spezifikationen) sind nicht für die Verarbeitung vieler Objekte innerhalb einer Transaktion ausgelegt, da davon ausgegangen wird, dass alles, was geladen wird, im Cache der ersten Ebene verbleiben sollte (daher ist dies clear()in JPA erforderlich ).

Außerdem empfehle ich eine Lösung auf niedrigerer Ebene, da der Overhead von ORM (Reflexion ist nur eine Spitze eines Eisbergs) möglicherweise so bedeutend ist, dass das Durchlaufen von Ebenen ResultSet, selbst wenn eine leichte Unterstützung wie erwähnt verwendet JdbcTemplatewird, viel schneller ist.

JPA ist einfach nicht dafür ausgelegt, Operationen an einer großen Anzahl von Entitäten auszuführen. Sie könnten mit flush()/ spielen clear(), um OutOfMemoryErrordies zu vermeiden , aber denken Sie noch einmal darüber nach. Sie gewinnen sehr wenig, wenn Sie den Preis für einen enormen Ressourcenverbrauch bezahlen.


Der Vorteil von JPA ist nicht nur die Datenbankunabhängigkeit, sondern auch die Möglichkeit, nicht einmal eine herkömmliche Datenbank (NoSQL) zu verwenden. Es ist nicht allzu schwer, ab und zu zu spülen / zu löschen, und normalerweise werden Chargenvorgänge selten durchgeführt.
Adam Gent

1
Hallo Thomasz. Ich habe viele Gründe, mich über JPA / Hibernate zu beschweren, aber respektvoll bezweifle ich wirklich, dass sie "nicht für die Bearbeitung vieler Objekte ausgelegt sind". Ich vermute, dass ich nur das richtige Muster für diesen Anwendungsfall lernen muss.
George Armhold

4
Nun, ich kann mir nur zwei Muster vorstellen: Paginierungen (mehrmals erwähnt) und flush()/ clear(). Das erste ist meiner Meinung nach nicht für die Stapelverarbeitung konzipiert, während die Folge von Flush () / Clear () nach undichter Abstraktion riecht .
Tomasz Nurkiewicz

Ja, es war eine Kombination aus Paginierung und Flush / Clear, wie Sie erwähnt haben. Vielen Dank!
George Armhold

7

Wenn Sie EclipseLink verwenden, verwende ich diese Methode, um das Ergebnis als Iterable zu erhalten

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();  
}  

Methode schließen

static void closeCursor(Iterable<?> list)
{
  if (list.iterator() instanceof Cursor)
    {
      ((Cursor) list.iterator()).close();
    }
}

6
Schönes jQuery- Objekt
usr-local-ΕΨΗΕΛΩΝ

Ich habe Ihren Code ausprobiert, aber trotzdem OOM erhalten - es scheint, dass alle T-Objekte (und alle verbundenen Tabellenobjekte, auf die von T verwiesen wird) niemals GC sind. Die Profilerstellung zeigt, dass auf sie aus "Tabelle" in org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork zusammen mit org.eclipse.persistence.internal.identitymaps.CacheKey verwiesen wird. Ich habe in den Cache geschaut und meine Einstellungen sind alle Standardeinstellungen (Selektiv deaktivieren, Schwach mit weichem Subcache, Cache-Größe 100, Löschen ungültig). Ich werde mich mit dem Deaktivieren von Sitzungen befassen und prüfen, ob dies hilfreich ist. Übrigens iteriere ich einfach über den Rückgabecursor mit "for (To: results)".
Edi Bice

Badum tssssssss
dctremblay

5

Dies hängt von der Art der Operation ab, die Sie ausführen müssen. Warum schleifen Sie über eine Million Zeilen? Aktualisieren Sie etwas im Batch-Modus? Werden Sie einem Client alle Datensätze anzeigen? Berechnen Sie einige Statistiken zu den abgerufenen Entitäten?

Wenn Sie dem Client eine Million Datensätze anzeigen möchten, überdenken Sie bitte Ihre Benutzeroberfläche. In diesem Fall besteht die geeignete Lösung darin, Ihre Ergebnisse zu paginieren und setFirstResult()und zu verwenden setMaxResult().

Wenn Sie ein Update für eine große Anzahl von Datensätzen gestartet haben, sollten Sie das Update einfach halten und verwenden Query.executeUpdate(). Optional können Sie das Update im asynchronen Modus mit einem Message-Driven Bean oa Work Manager ausführen.

Wenn Sie Statistiken zu den abgerufenen Entitäten berechnen, können Sie die in der JPA-Spezifikation definierten Gruppierungsfunktionen nutzen.

Für jeden anderen Fall bitte genauer sein :)


Ganz einfach, ich muss "für jede" Zeile etwas tun. Dies ist sicherlich ein häufiger Anwendungsfall. In dem speziellen Fall, an dem ich gerade arbeite, muss ich einen externen Webdienst, der sich vollständig außerhalb meiner Datenbank befindet, mit einer ID (PK) aus jeder Zeile abfragen. Die Ergebnisse werden in keinem Client-Webbrowser wieder angezeigt, sodass keine nennenswerte Benutzeroberfläche vorhanden ist. Mit anderen Worten, es ist ein Batch-Job.
George Armhold

Wenn Sie eine Druck-ID für jede Zeile "benötigen", gibt es keine andere Möglichkeit, als jede Zeile abzurufen, eine ID abzurufen und zu drucken. Die beste Lösung hängt davon ab, was Sie tun müssen.
Dainius

@Caffeine Coma, wenn Sie nur die ID jeder Zeile benötigen, würde die größte Verbesserung wahrscheinlich darin bestehen, nur diese Spalte abzurufen SELECT m.id FROM Model mund dann über eine Liste <Integer> zu iterieren.
Jörn Horstmann

1
@ Jörn Horstmann- Wenn es Millionen von Zeilen gibt, ist das wirklich wichtig? Mein Punkt ist, dass eine ArrayList mit Millionen von Objekten (wie klein sie auch sein mögen) nicht gut für den JVM-Heap ist.
George Armhold

@Dainius: Meine Frage lautet wirklich: "Wie kann ich über jede Zeile iterieren, ohne die gesamte ArrayList im Speicher zu haben?" Mit anderen Worten, ich hätte gerne eine Schnittstelle zum Ziehen von N zu einem Zeitpunkt, an dem N deutlich kleiner als 1 Million ist. :-)
George Armhold

5

Es gibt kein "richtiges" Vorgehen, dies ist nicht das, was JPA oder JDO oder ein anderes ORM tun sollen. Gerade JDBC ist Ihre beste Alternative, da Sie es so konfigurieren können, dass eine kleine Anzahl von Zeilen zurückgebracht wird eine Zeit und leeren Sie sie, wie sie verwendet werden, deshalb gibt es serverseitige Cursor.

ORM-Tools sind nicht für die Massenverarbeitung konzipiert. Sie dienen dazu, Objekte zu manipulieren und zu versuchen, das RDBMS, in dem die Daten gespeichert sind, so transparent wie möglich zu gestalten. Die meisten Fehler treten zumindest teilweise im transparenten Bereich auf. In dieser Größenordnung gibt es keine Möglichkeit, Hunderttausende von Zeilen (Objekten), geschweige denn Millionen, mit einem ORM zu verarbeiten und es in angemessener Zeit ausführen zu lassen, da der Aufwand für die Objektinstanziierung schlicht und einfach ist.

Verwenden Sie das entsprechende Werkzeug. Straight JDBC und Stored Procedures haben 2011 definitiv einen Platz, insbesondere was sie im Vergleich zu diesen ORM-Frameworks besser können.

Eine Million von irgendetwas zu ziehen, selbst in eine einfache, List<Integer>wird nicht sehr effizient sein, unabhängig davon, wie Sie es tun. Der richtige Weg, um das zu tun, was Sie verlangen, ist einfach SELECT id FROM table, auf SERVER SIDE(herstellerabhängig) gesetzt und der Cursor darauf FORWARD_ONLY READ-ONLYund iteriert darüber.

Wenn Sie wirklich Millionen von IDs zur Verarbeitung ziehen, indem Sie jeweils einen Webserver aufrufen, müssen Sie auch eine gleichzeitige Verarbeitung durchführen, damit diese in einer angemessenen Zeit ausgeführt werden kann. Das Ziehen mit einem JDBC-Cursor und das gleichzeitige Platzieren einiger davon in einer ConcurrentLinkedQueue sowie das Ziehen und Verarbeiten eines kleinen Pools von Threads (# CPU / Cores + 1) ist die einzige Möglichkeit, Ihre Aufgabe auf einem Computer mit einem beliebigen " normale "RAM-Größe, vorausgesetzt, Sie haben bereits nicht genügend Speicher.

Siehe diese Antwort .


1
Sie sagen also, dass kein Unternehmen jemals jede Zeile seiner Benutzertabelle besuchen muss? Ihre Programmierer werfen Hibernate einfach aus dem Fenster, wenn es Zeit dafür ist? " Es gibt keine Möglichkeit, Hunderttausende von Zeilen zu verarbeiten " - in meiner Frage habe ich auf setFirstResult / setMaxResult hingewiesen, also gibt es eindeutig einen Weg. Ich frage, ob es einen besseren gibt.
George Armhold

"Eine Million von irgendetwas in eine einfache Liste <Integer> zu ziehen, wird nicht sehr effizient sein, unabhängig davon, wie Sie es tun." Das ist genau mein Punkt. Ich frage, wie man nicht die Riesenliste erstellt, sondern eine Ergebnismenge durchläuft.
George Armhold

Verwenden Sie eine einfache gerade JDBC-Select-Anweisung mit einem FORWARD_ONLY READ_ONLY und einem SERVER_SIDE-Cursor, wie in meiner Antwort vorgeschlagen. Wie JDBC einen SERVER_SIDE-Cursor verwendet, hängt vom Datenbanktreiber ab.

1
Ich stimme der Antwort voll und ganz zu. Die beste Lösung hängt vom Problem ab. Wenn das Problem darin besteht, ein paar Entitäten leicht zu laden, ist JPA gut. Wenn das Problem darin besteht, große Datenmengen effizient zu nutzen, ist direktes JDBC besser.
Extraneon

4
Das Durchsuchen von Millionen von Datensätzen ist aus einer Reihe von Gründen üblich, beispielsweise um sie in einer Suchmaschine zu indizieren. Und obwohl ich damit einverstanden bin, dass JDBC normalerweise ein direkterer Weg ist, gehen Sie manchmal in ein Projekt, in dem bereits eine sehr komplexe Geschäftslogik in einer Ruhezustandsebene gebündelt ist. Wenn Sie es umgehen und zu JDBC wechseln, umgehen Sie die Geschäftslogik, deren Neuimplementierung und Wartung manchmal nicht trivial ist. Wenn Leute Fragen zu atypischen Anwendungsfällen stellen, wissen sie oft, dass dies etwas seltsam ist, erben jedoch möglicherweise etwas oder bauen von Grund auf neu und können möglicherweise keine Details offenlegen.
Mark Bennett

4

Sie können einen anderen "Trick" verwenden. Laden Sie nur eine Sammlung von Bezeichnern der Entitäten, an denen Sie interessiert sind. Angenommen, der Bezeichner ist vom Typ long = 8 Byte, dann ergibt eine Liste solcher Bezeichner 10 ^ 6 ungefähr 8 MB. Wenn es sich um einen Stapelprozess handelt (jeweils eine Instanz), ist dies erträglich. Dann iterieren Sie einfach und erledigen Sie den Job.

Eine weitere Bemerkung - Sie sollten dies sowieso in Blöcken tun - insbesondere, wenn Sie Datensätze ändern, da sonst das Rollback-Segment in der Datenbank wächst.

Wenn es darum geht, die firstResult / maxRows-Strategie festzulegen, ist es SEHR SEHR langsam für Ergebnisse, die weit von der Spitze entfernt sind.

Berücksichtigen Sie auch, dass die Datenbank wahrscheinlich in einer Lese-Commit-Isolation arbeitet , um zu vermeiden, dass Phantom-Lesevorgänge Bezeichner laden und dann Entitäten einzeln (oder 10 x 10 oder was auch immer) laden.


Hallo @Marcin, können Sie oder jemand anderes einen Link zu Beispielcode bereitstellen, der diesen schrittweisen und id-first-schrittweisen Ansatz anwendet, vorzugsweise unter Verwendung von Java8-Streams?
Krevelen

2

Ich war überrascht zu sehen, dass die Verwendung gespeicherter Prozeduren in den Antworten hier nicht mehr im Vordergrund stand. In der Vergangenheit habe ich, wenn ich so etwas tun musste, eine gespeicherte Prozedur erstellt, die Daten in kleinen Blöcken verarbeitet, dann eine Weile schläft und dann fortfährt. Der Grund für das Schlafen ist, die Datenbank nicht zu überfordern, die vermutlich auch für Echtzeit-Abfragetypen verwendet wird, z. B. für die Verbindung mit einer Website. Wenn die Datenbank von niemand anderem verwendet wird, können Sie den Schlaf auslassen. Wenn Sie sicherstellen möchten, dass Sie jeden Datensatz einmal und nur einmal verarbeiten, müssen Sie eine zusätzliche Tabelle (oder ein zusätzliches Feld) erstellen, um zu speichern, welche Datensätze Sie verarbeitet haben, um bei Neustarts stabil zu sein.

Die Leistungseinsparungen sind erheblich und möglicherweise um Größenordnungen schneller als alles, was Sie in JPA / Hibernate / AppServer-Land tun können, und Ihr Datenbankserver verfügt höchstwahrscheinlich über einen eigenen serverseitigen Cursortyp für die effiziente Verarbeitung großer Ergebnismengen. Die Leistungseinsparungen ergeben sich daraus, dass die Daten nicht vom Datenbankserver an den Anwendungsserver gesendet werden müssen, wo Sie die Daten verarbeiten und dann zurücksenden.

Die Verwendung gespeicherter Prozeduren hat einige erhebliche Nachteile, die dies für Sie möglicherweise vollständig ausschließen. Wenn Sie diese Fähigkeit jedoch in Ihrer persönlichen Toolbox haben und sie in solchen Situationen einsetzen können, können Sie diese Art von Dingen ziemlich schnell ausschalten .


1
-2 Downvotes - Würde der nächste Downvoter bitte Ihre Downvote verteidigen?
Gefahr

1
Ich dachte das Gleiche, als ich diese las. Die Frage zeigt einen Batch-Job mit hohem Volumen ohne Benutzeroberfläche an. Angenommen, Sie benötigen keine App-Server-spezifischen Ressourcen. Warum sollten Sie überhaupt einen App-Server verwenden? Gespeicherte Prozeduren wären viel effizienter.
jdessey

@jdessey Nehmen wir an, wir haben je nach Situation eine Importfunktion, bei der beim Import etwas mit einem anderen Teil des Systems geschehen sollte, z. B. Zeilen zu einer anderen Tabelle hinzufügen, basierend auf einigen Geschäftsregeln, die bereits als EJB codiert wurden. Dann wäre die Ausführung auf einem App-Server sinnvoller, es sei denn, Sie können die EJB in einem eingebetteten Modus ausführen.
Archimedes Trajano

1

Um die Antwort von @Tomasz Nurkiewicz zu erweitern. Sie haben Zugriff auf die, DataSourcedie Ihnen wiederum eine Verbindung herstellen kann

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

In Ihrem Code haben Sie

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

Auf diese Weise können Sie JPA für einige bestimmte Großstapelvorgänge wie Import / Export umgehen. Bei Bedarf haben Sie jedoch weiterhin Zugriff auf den Entitätsmanager für andere JPA-Vorgänge.


0

Verwenden Sie PaginationConcept, um das Ergebnis abzurufen


4
Die Paginierung ist sehr gut für GUIs. Für die Verarbeitung großer Datenmengen wurde das ScrollableResultSet jedoch vor langer Zeit erfunden. Es ist einfach nicht in JPA.
Extraneon

0

Ich habe mich das selbst gefragt. Es scheint wichtig zu sein:

  • wie groß Ihr Datensatz ist (Zeilen)
  • Welche JPA-Implementierung verwenden Sie?
  • Welche Art von Verarbeitung führen Sie für jede Zeile durch?

Ich habe einen Iterator geschrieben, um das Austauschen beider Ansätze zu vereinfachen (findAll vs findEntries).

Ich empfehle Ihnen, beide zu versuchen.

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();
    }

}

Am Ende habe ich meinen Chunk-Iterator nicht verwendet (daher ist er möglicherweise nicht so getestet). Übrigens benötigen Sie Google-Sammlungen, wenn Sie es verwenden möchten.


In Bezug auf "welche Art von Verarbeitung Sie für jede Zeile ausführen" - wenn die Anzahl der Zeilen in Millionen liegt, vermute ich, dass selbst ein einfaches Objekt mit nur einer ID-Spalte Probleme verursachen wird. Ich habe auch darüber nachgedacht, meinen eigenen Iterator zu schreiben, der setFirstResult / setMaxResult umschließt, aber ich dachte mir, dass dies ein häufiges (und hoffentlich gelöstes!) Problem sein muss.
George Armhold

@Caffeine Coma Ich habe meinen Iterator gepostet. Sie könnten wahrscheinlich noch etwas JPA daran anpassen. Sag mir, ob es hilft. Ich habe es letztendlich nicht benutzt (habe ein findAll gemacht).
Adam Gent

0

Im Ruhezustand gibt es 4 verschiedene Möglichkeiten, um das zu erreichen, was Sie wollen. Jedes hat Design-Kompromisse, Einschränkungen und Konsequenzen. Ich schlage vor, jedes zu erkunden und zu entscheiden, welches für Ihre Situation richtig ist.

  1. Verwenden Sie eine zustandslose Sitzung mit scroll ()
  2. Verwenden Sie session.clear () nach jeder Iteration. Wenn andere Entitäten angehängt werden müssen, laden Sie sie in einer separaten Sitzung. Tatsächlich emuliert die erste Sitzung die zustandslose Sitzung, behält jedoch alle Funktionen einer zustandsbehafteten Sitzung bei, bis die Objekte getrennt werden.
  3. Verwenden Sie iterate () oder list (), erhalten Sie jedoch nur IDs in der ersten Abfrage und dann in einer separaten Sitzung in jeder Iteration. Führen Sie session.load aus und schließen Sie die Sitzung am Ende der Iteration.
  4. Verwenden Sie Query.iterate () mit EntityManager.detach () aka Session.evict ();

0

Hier ist ein einfaches, direktes JPA-Beispiel (in Kotlin), das zeigt, wie Sie über eine beliebig große Ergebnismenge paginieren können, indem Sie Teile von 100 Elementen gleichzeitig lesen, ohne einen Cursor zu verwenden (jeder Cursor verbraucht Ressourcen in der Datenbank). Es verwendet die Keyset-Paginierung.

Unter https://use-the-index-luke.com/no-offset finden Sie das Konzept der Keyset-Paginierung und unter https://www.citusdata.com/blog/2016/03/30/five-ways-to- paginieren / für einen Vergleich verschiedener Arten der Paginierung mit ihren Nachteilen.

/*
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)
}

0

Ein Beispiel, bei dem JPA und NativeQuery jedes Mal die Größe der Elemente mithilfe von Offsets abrufen

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;
    }
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.