PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

HINT_PASS_DISTINCT_THROUGH réduit le nombre d'entités renvoyées par page pour une PageRequest en dessous de la taille de page configurée (PostgreSQL)

Le problème que vous rencontrez est lié à la façon dont vous utilisez le HINT_PASS_DISTINCT_THROUGH indice.

Cet indice vous permet d'indiquer à Hibernate que le DISTINCT le mot-clé ne doit pas être utilisé dans le SELECT déclaration émise contre la base de données.

Vous profitez de ce fait pour permettre à vos requêtes d'être triées par un champ qui n'est pas inclus dans le DISTINCT liste des colonnes.

Mais ce n'est pas ainsi que cet indice doit être utilisé.

Cet indice ne doit être utilisé que lorsque vous êtes sûr qu'il n'y aura pas de différence entre appliquer ou non un DISTINCT mot-clé au SQL SELECT déclaration, car le SELECT l'instruction va déjà récupérer toutes les valeurs distinctes en soi . L'idée est d'améliorer les performances de la requête en évitant l'utilisation d'un DISTINCT inutile déclaration.

C'est généralement ce qui se passe lorsque vous utilisez le query.distinct méthode dans vos requêtes de critères, et vous êtes en train de join fetching relations avec les enfants. Ce super article de @VladMihalcea expliquent en détail le fonctionnement de l'indice.

D'autre part, lorsque vous utilisez la pagination, il définira OFFSET et LIMIT - ou quelque chose de similaire, selon la base de données sous-jacente - dans le SQL SELECT déclaration émise contre la base de données, limitant à un nombre maximum de résultats votre requête.

Comme indiqué, si vous utilisez le HINT_PASS_DISTINCT_THROUGH indice, le SELECT l'instruction ne contiendra pas le DISTINCT mot-clé et, à cause de vos jointures, cela pourrait potentiellement donner des enregistrements en double de votre entité principale. Ces enregistrements seront traités par Hibernate pour différencier les doublons, car vous utilisez query.distinct , et il supprimera en fait les doublons si nécessaire. Je pense que c'est la raison pour laquelle vous pouvez obtenir moins d'enregistrements que demandé dans votre Pageable .

Si vous supprimez l'indice, comme le DISTINCT mot-clé est passé dans l'instruction SQL qui est envoyée à la base de données, dans la mesure où vous ne projetez que les informations de l'entité principale, il récupérera tous les enregistrements indiqués par LIMIT et c'est pourquoi il vous donnera toujours le nombre d'enregistrements demandé.

Vous pouvez essayer de fetch join vos entités enfants (au lieu de seulement join avec eux). Cela éliminera le problème de ne pas pouvoir utiliser le champ dont vous avez besoin pour trier dans les colonnes du DISTINCT mot-clé et, en plus, vous pourrez appliquer, désormais légitimement, l'indice.

Mais si vous le faites, cela vous posera un autre problème :si vous utilisez le join fetch et la pagination, pour renvoyer les entités principales et ses collections, Hibernate n'appliquera plus la pagination au niveau de la base de données - il n'inclura pas OFFSET ou LIMIT mots-clés dans l'instruction SQL, et il essaiera de paginer les résultats en mémoire. C'est le fameux Hibernate HHH000104 avertissement :

HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!

@VladMihalcea explique cela en détail dans la dernière partie de ceci article.

Il a également proposé une solution possible à votre problème, Fonctions de fenêtre .

Dans votre cas d'utilisation, au lieu d'utiliser Specification s, l'idée est que vous implémentiez votre propre DAO. Ce DAO doit uniquement avoir accès au EntityManager , ce qui n'est pas beaucoup car vous pouvez injecter votre @PersistenceContext :

@PersistenceContext
protected EntityManager em;

Une fois que vous avez ce EntityManager , vous pouvez créer des requêtes natives et utiliser des fonctions de fenêtre pour créer, en fonction du Pageable fourni informations, la bonne instruction SQL qui sera émise sur la base de données. Cela vous donnera beaucoup plus de liberté sur les champs à utiliser pour le tri ou tout ce dont vous avez besoin.

Comme l'indique le dernier article cité, Window Functions est une fonctionnalité prise en charge par toutes les bases de données principales.

Dans le cas de PostgreSQL, vous pouvez facilement les trouver dans la documentation officielle .

Enfin, une autre option, suggérée en fait par @nickshoe, et expliquée en détail dans le article il a cité, consiste à effectuer le processus de tri et de pagination en deux phases :dans la première phase, vous devez créer une requête qui référencera vos entités enfants et dans laquelle vous appliquerez la pagination et le tri. Cette requête vous permettra d'identifier les identifiants des entités principales qui seront utilisées, dans la deuxième phase du processus, pour obtenir les entités principales elles-mêmes.

Vous pouvez tirer parti du DAO personnalisé susmentionné pour accomplir ce processus.