Hibernate iterate

ich setze in den letzten Jahren zunehmen iterate() statt list() bei Hibernate-Queries ein,
mit nicht allzu genauer Zielvorstellung, aber ich laufe die Ergebnisse oft eh nur einmal durch, brauche keine 100.000er Liste,

wenigstens diese eingespart und vielleicht auch sonstwie weniger Last gleichzeitig im Speicher,
durchaus auch möglich dass mal nach wenigen Tausend abgebrochen wird,
-> falls der Rest schon in Rohdaten da ist, dann vielleicht zumindest nicht weiter aufbereitet…

aktuell fällt mir erstmals auf, dass bei einer Query, die 100 gemappte Entitys (statt berechneter Daten) lädt,
am Anfang die Ids und dann alle Einträge in einzelnen SQL-Queries geladen werden!

zum Glück war da die DB langsam, dauerte 5 sec statt 0.1sec, inzwischen Unterschied nicht mehr feststellbar,
aber ich nutze nun Statistics der SessionFactory, getPrepareStatementCount() deckt anscheinend zum Glück (als einziges) auch das interne SQL ab


gibt es Einstellungen, so dass in diesem Fall mehrere auf einmal geladen werden statt einzeln?
hibernate.jdbc.fetch_size usw. bewirken hier nichts?
wobei ich mit Abfrage aller Ids und dann neuen Queries zu je 10 einzeln angegebenen Ids auch nicht glücklich wäre…,

kann man das Verhalten anderer iterate-Queries mit nur 1x SQL auch für Entity-Queries erzwingen?


aus Gewohnheit vermeide ich meist eh in Auswertungen, Entitys zu laden, so dass Hibernate sie nicht hinsichlich Änderungen cacht/ prüfen muss usw.,
nur Nutzdaten in Dummy-Objekten, das wird dann hier auch helfen

jedenfalls aufzupassen, sonstige Anmerkungen/ Artikel in diesem Bereich?

Zu Optimierungen von iterate nicht direkt. Aber das hier:

[quote=SlaterB]gibt es Einstellungen, so dass in diesem Fall mehrere auf einmal geladen werden statt einzeln?
hibernate.jdbc.fetch_size usw. bewirken hier nichts?
wobei ich mit Abfrage aller Ids und dann neuen Queries zu je 10 einzeln angegebenen Ids auch nicht glücklich wäre…,[/quote]
hat mich stark an paged ResultSets erinnert. Das habe ich (wenig überraschend) in einer Webanwendung umgesetzt. Wenn man danach googelt, wird man auf die API-Methoden setFirstResult und setMaxResult verwiesen. Das benutzend habe ich mir den Execution Plan der DB (in dem Fall HSQL) für das generierte SQL-Statement angeschaut. Das enthielt den unerwünschten Full Table Scan. Meine eigengestrickte JPQL-Query dann nicht mehr.

Warum schreibe ich das?

  • Viele kleine Queries müssen nicht unbedingt schlechter sein, als eine schlechte „große“
  • Wenn Dich die Optimierung der DB-Performance interessiert, schaue Dir die Execution Plans der DB für ausgewählte Statements an.
  • Hinter dem Ansatz, jede einzelne Id bei der Iteration einzeln abzufragen, steckt wohl das Prinzip „lade nur, was du gerade brauchst“. Denn bei jeder Entity besteht ja die Gefahr, dass sie veraltet ist, wenn sich Ihr Inhalt in der DB geändert hat. Darum wohl erstmal nur Ids (die ändern sich ja nicht, außer durch Löschen) und dann den Rest einzeln bei jedem Iterationsschritt.
  • Pagination ist für mich eine Zwischenform zwischen den Extremen „ganze Liste“ und „iterate“. Vielleicht genau deswegen für dich auch interessant. Es klingt jedenfalls nach einer möglichen Antwort auf:

Ist doch genau das gewünschte Verhalten

iterate -> holt nur die id’s, und die Entities nur bei Bedarf (führt uU zu n+1 DB-Abfragen)

**Return the query results as an Iterator. If the query contains multiple results pre row, the results are returned in an instance of Object[].
Entities returned as results are initialized on demand. The first SQL query returns identifiers only.
**

list -> holt alles auf einmal (1 DB Abfrage)

Hängt doch davon ab, was man gerade macht?

@nillehammer
um es Hibernate intern einzubauen wäre das eine naheliegende Variante, aber soweit wird es ja nicht gehen,
für mich nicht so interessant, ich habe nur iterate() und das sollte angemessen funktionieren

100 einzelne Queries für 100 Entities kommt dazu nicht nicht in Frage, ob diese manchmal besser sein können als eine große, hmm,
allein schon durch Log- und Zähler-Spam zu vermeiden,
und was intern an tausenden Methodenaufrufen Overhead je Query, Untersuchung des Inhalts, bei Hibernate und in DB stattfinden will ich lieber gar nicht abschätzen,
reale virtuelle Netzwerkübertragung mit allem drum und dran sowieso

und die DB oder Verbindung hat eben manchmal einen schlechten Tag, glaube ich, so dass jede Query pauschal 100ms länger dauert → große Verzögerung

in bedenklicher Weise angewandt,
bei jedem simplen Datei-Lesevorgang mit BufferedInputStream wird an ByteArray von 8KB gedacht statt jedes Byte einzeln…

eine Pauschallösung gibt es vielleicht nicht auf Anzahl Entitys zu lesen, Größenunterschied mit Joins & Co. kann erheblich sein,
hibernate.jdbc.fetch_size haben das gleiche Problem, programmweit einzustellen etwas fraglich, aber ist zumindest da,

hier schlicht 1 und Ende, das kann es doch nicht sein

[quote=SlaterB]in bedenklicher Weise angewandt,
bei jedem simplen Datei-Lesevorgang mit BufferedInputStream wird an ByteArray von 8KB gedacht statt jedes Byte einzeln…[/quote]
Da hinkt der Vergleich etwas. Bei Dateien würde man eher weniger Concurrent Modifications erwarten als bei DB-Entities. Die lassen sich eben nur mit sehr viel mehr Overhead cachen.

Auch wenn eine Art pre-fetch für den Iterator sinnvoll klingen mag, so muss man konstatieren, dass es das hier (leider) nicht gibt. Das “Leider” in Klammern, weil es Alternativen gibt. Vielleicht ist iterate für Deinen Anwendungsfall nicht die ideale Lösung. Ob es das angesichts seines Verhaltens für überhaupt irgend einen Anwendungsfall ist, das ist eine berechtigte Frage…

[QUOTE=Bleiglanz]Ist doch genau das gewünschte Verhalten

iterate → holt nur die id’s, und die Entities nur bei Bedarf (führt uU zu n+1 DB-Abfragen)

**Return the query results as an Iterator. If the query contains multiple results pre row, the results are returned in an instance of Object.
Entities returned as results are initialized on demand. The first SQL query returns identifiers only.
**

list → holt alles auf einmal (1 DB Abfrage)

Hängt doch davon ab, was man gerade macht?[/QUOTE]
:daumen:

Genau :slight_smile:

Kommt ganz auf den Kontext an.

java - difference between query.list and query.iterate - Stack Overflow

If instances are already in the session (primary-level cache) or second-level cache iterate() will give better performance.

If they are not already cached, iterate() will be slower than list() and might require many database hits for a simple query.

„If the query contains multiple results pre row“
bemerkenswerter Tippfehler in der API,
edit: findet sich in Suchmaschinen mehrfach, sogar hierhin gewandert:

edit: auch ein Zitat der API von mir im alten Forum aus 2011 auf Seite 1 zur Suche „results pre row“ :wink:


ob Hibernate bei Entities erst Ids laden, Cache prüfen und nur fehlende nachladen sollte (einzeln oder gesammelt), oder alles auf einmal in einer Query mit potentiell viel Doppelten,
das passt doch besser in separate interne, für die Verwendung unsichtbare Query-Einstellungen, genau wie LazyLoading, wieso das über list() vs iterate() regeln?


list() vs iterate() ist bei jeder Query allgemein von Bedeutung, auch bei SQL, auch bei HQL welches komplett neue Id-lose Daten zusammenstellt,
wichtig ist da für mich viel mehr, nicht sämtliche Daten auf einmal im Speicher zu haben, vielleicht auch nicht sämtliche auf einmal aus der DB zu laden,

gibt es nicht auch längere offene Anfragen an die DB, erste 1000 Ergebnisse laden, prüfen, Abbruch oder nächste 1000 laden?
ob Query.iterate() das macht wäre natürlich erst zu hoffen, könnte auch die Liste laden und darauf iterate() zurückgeben :wink: ,

aber ich sehe schon oft früheren Beginn als bei Warten auf alle Ergebnisse,
und anbei ein Task-Screenshot mit erst list()/ iterator() auf einen Schlag, sowie später mit Pausen in der Iterator-Verarbeitung

ist dieses Verhalten nicht eine fundamental wichtigere Aufgabe für DB-Verbindung als schnöder Cache? :wink:
freilich mehr für reines SQL, 100.000 Einträge abgefragt, als HQL mit gemappten einzelnen wichtigen Objekten, persist & Co.

Der wichtige Teil der JavaDoc steht dann im naechsten Satz zu iterate:

Entities returned as results are initialized on demand. The first SQL query returns identifiers only.

Ist zwar oberflaechlich ausgedrueckt, aber das „initialized on demand“ und „the first SQL query returns identifiers only“ deutet doch schon stark auf N+1 hin.
Iterate und list sind also nicht belibieg austauschbar wenn es um Performance geht.

Wenn man nur di IDs braucht und sonst nix ist iterate schon mal nicht verkehrt, ansonsten eben list, mit Query.setMaxResults kann man schon eingrenzen wieviele es maximal geben soll, also je nach UseCase, meist eben listy() verwenden.

jaja, die API schreibt es recht deutlich, aber trotzdem bisschen zu diskutieren, wer verwendet das so je?

in Bereichen, in denen Objekte im überschaubaren Maße im Cache liegen kommt es auf das Doppelt-Laden von List doch nicht so sehr an,
letztlich bekommt man eh nur die einen Cache-Objekte, das ist ja wichtig

wie kann man das verwenden? wenn man next() auf den Iterator aufruft bekommt man doch das initialisierte Objekt?

fragt man dagegen nur die Ids ab "select id from Klasse", dann ist es wieder eine neue Daten-Query, wo Cache und Co. nichts zu sagen haben, das ist Ironie,
dann kommen wieder alle anderen Punkte ins Spiel, sämtliche Daten auf einmal laden laden oder nur die ersten und Query in der DB offen gehalten usw.

‚wenn man nur die Ids aus der DB abfragen und die Objekte aus dem Cache dazu holen will, man weiß dass die meisten dort liegen‘, ok, das ginge


edit:
aber es fiel mir auch nur deshalb so lange nicht auf weil Abfragen direkt auf Entities mit Links auf anderen Entities, eben potentielle Cache-Verwirrungen usw. sowieso ungeeignet sind, jedenfalls für Massenauswertungen,
hatte nur gerade eine neue Entity recht schlank gebaut, aber auch die kann ich nun also für direkte Abfrage vergessen…,

paar Attribute in neuen Dummy-Objekt zusammengestellt tut es aber genauso, dann arbeitet auch iterate() wieder wie gewünscht :wink: