Das Problem, mit dem Sie experimentieren, hat damit zu tun, wie Sie HINT_PASS_DISTINCT_THROUGH
verwenden Hinweis.
Mit diesem Hinweis können Sie Hibernate angeben, dass der DISTINCT
Schlüsselwort sollte nicht in SELECT
verwendet werden Anweisung, die gegenüber der Datenbank ausgestellt wurde.
Sie nutzen diese Tatsache aus, damit Ihre Abfragen nach einem Feld sortiert werden können, das nicht im DISTINCT
enthalten ist Spaltenliste.
Aber so sollte dieser Hinweis nicht verwendet werden.
Dieser Hinweis darf nur verwendet werden, wenn Sie sicher sind, dass es keinen Unterschied macht, ob Sie einen DISTINCT
anwenden oder nicht Schlüsselwort zum SQL SELECT
-Anweisung, weil die SELECT
-Anweisung wird bereits alle eindeutigen Werte per se abrufen . Die Idee ist, die Leistung der Abfrage zu verbessern, indem die Verwendung eines unnötigen DISTINCT
vermieden wird Aussage.
Dies geschieht normalerweise, wenn Sie query.distinct
verwenden Methode in Ihren Kriterienabfragen, und Sie join fetching
kindliche Beziehungen. Dieser großartige Artikel
von @VladMihalcea erklären, wie der Hinweis im Detail funktioniert.
Wenn Sie dagegen Paging verwenden, wird OFFSET
gesetzt und LIMIT
- oder etwas ähnliches, abhängig von der zugrunde liegenden Datenbank - im SQL SELECT
Anweisung, die gegenüber der Datenbank ausgegeben wird und Ihre Abfrage auf eine maximale Anzahl von Ergebnissen beschränkt.
Wie gesagt, wenn Sie den HINT_PASS_DISTINCT_THROUGH
verwenden Hinweis, das SELECT
-Anweisung enthält nicht den DISTINCT
Stichwort und aufgrund Ihrer Verknüpfungen möglicherweise doppelte Datensätze Ihrer Hauptentität. Diese Datensätze werden von Hibernate verarbeitet, um Duplikate zu unterscheiden, da Sie query.distinct
verwenden , und es werden bei Bedarf tatsächlich Duplikate entfernt. Ich denke, das ist der Grund, warum Sie möglicherweise weniger Datensätze erhalten, als in Ihrem Pageable
angefordert werden .
Wenn Sie den Hinweis entfernen, als DISTINCT
Schlüsselwort in der SQL-Anweisung übergeben wird, die an die Datenbank gesendet wird, sofern Sie nur Informationen der Hauptentität projizieren, werden alle Datensätze abgerufen, die durch LIMIT
angegeben sind und deshalb erhalten Sie immer die angeforderte Anzahl von Datensätzen.
Sie können versuchen, fetch join
Ihre untergeordneten Entitäten (anstatt nur join
mit ihnen). Dadurch wird das Problem beseitigt, dass Sie das Feld, nach dem Sie sortieren müssen, in den Spalten des DISTINCT
nicht verwenden können Schlüsselwort und außerdem können Sie jetzt berechtigterweise den Hinweis anwenden.
Aber wenn Sie dies tun, werden Sie ein weiteres Problem haben:Wenn Sie Join-Fetch und Paginierung verwenden, um die Hauptentitäten und ihre Sammlungen zurückzugeben, wird Hibernate keine Paginierung mehr auf Datenbankebene anwenden - es wird OFFSET
nicht enthalten oder LIMIT
Schlüsselwörter in der SQL-Anweisung, und es wird versucht, die Ergebnisse im Speicher zu paginieren. Dies ist das berühmte Hibernate HHH000104
Warnung:
HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!
@VladMihalcea erklärt das ausführlich im letzten Teil von diesem Artikel.
Er schlug auch eine mögliche Lösung für Ihr Problem vor, Window Functions .
Verwenden Sie in Ihrem Anwendungsfall anstelle von Specification
s, die Idee ist, dass Sie Ihr eigenes DAO implementieren. Dieses DAO muss nur Zugriff auf den EntityManager
haben , was nicht viel ist, da Sie Ihren @PersistenceContext
einfügen können :
@PersistenceContext
protected EntityManager em;
Sobald Sie diesen EntityManager
haben , können Sie native Abfragen erstellen und Fensterfunktionen zum Erstellen verwenden, basierend auf dem bereitgestellten Pageable
Informationen, die richtige SQL-Anweisung, die für die Datenbank ausgegeben wird. Dies gibt Ihnen viel mehr Freiheit darüber, welche Felder zum Sortieren verwendet werden oder was auch immer Sie brauchen.
Wie der zuletzt zitierte Artikel zeigt, sind Fensterfunktionen eine Funktion, die von allen größeren Datenbanken unterstützt wird.
Im Fall von PostgreSQL finden Sie sie leicht in der offiziellen Dokumentation .
Zum Schluss noch eine Option, die tatsächlich von @nickshoe vorgeschlagen und ausführlich im Artikel Wie er zitierte, besteht darin, den Sortier- und Paging-Prozess in zwei Phasen durchzuführen:In der ersten Phase müssen Sie eine Abfrage erstellen, die auf Ihre untergeordneten Entitäten verweist und in der Sie Paging und Sortierung anwenden. Diese Abfrage ermöglicht es Ihnen, die IDs der Hauptentitäten zu identifizieren, die in der zweiten Phase des Prozesses verwendet werden, um die Hauptentitäten selbst zu erhalten.
Sie können das oben erwähnte benutzerdefinierte DAO nutzen, um diesen Prozess durchzuführen.