Étant donné le modèle de domaine suivant, je veux charger tous les Answer
s, y compris leurs Value
s et leurs sous-enfants respectifs, et les mettre dans un AnswerDTO
pour ensuite les convertir en JSON. J'ai une solution de travail mais elle souffre du problème N + 1 dont je veux me débarrasser en utilisant un ad-hoc @EntityGraph
. Toutes les associations sont configurées LAZY
.
@Query("SELECT a FROM Answer a")
@EntityGraph(attributePaths = {"value"})
public List<Answer> findAll();
En utilisant un ad-hoc @EntityGraph
sur la Repository
méthode, je peux m'assurer que les valeurs sont pré-récupérées pour empêcher N + 1 sur l' Answer->Value
association. Bien que mon résultat soit satisfaisant, il existe un autre problème N + 1, en raison du chargement paresseux de l' selected
association de l' MCValue
al.
Utiliser ceci
@EntityGraph(attributePaths = {"value.selected"})
échoue, car le selected
champ n'est bien sûr qu'une partie de certaines des Value
entités:
Unable to locate Attribute with the the given name [selected] on this ManagedType [x.model.Value];
Comment puis-je dire à JPA d'essayer de récupérer l' selected
association uniquement si la valeur est a MCValue
? J'ai besoin de quelque chose comme ça optionalAttributePaths
.
la source
selected
les réponses qui ont unMCValue
. Je n'aimais pas que cela nécessiterait une boucle supplémentaire et je devrais gérer la correspondance entre les ensembles de données. J'aime votre idée d'exploiter le cache Hibernate pour cela. Pouvez-vous expliquer dans quelle mesure (en termes de cohérence) il est sûr de s'appuyer sur le cache pour contenir les résultats? Est-ce que cela fonctionne lorsque les requêtes sont effectuées dans une transaction? J'ai peur des erreurs d'initialisation paresseuses difficiles à repérer et sporadiques.MCValue
entités. Et vous n'avez pas besoin d'une boucle supplémentaire. Vous devez récupérer toutes lesMCValue
entités avec 1 requête qui se joint àAnswer
et utilise la même clause WHERE que votre requête actuelle. J'en ai également parlé dans le flux en direct d'aujourd'hui: youtu.be/70B9znTmi00?t=238 Cela a commencé à 3h58 mais j'ai pris quelques autres questions entre les deux ...SINGLE_TABLE_INHERITANCE
.Je ne sais pas ce que Spring-Data fait là-bas, mais pour ce faire, vous devez généralement utiliser l'
TREAT
opérateur pour pouvoir accéder à la sous-association, mais l'implémentation de cet opérateur est assez boguée. Hibernate prend en charge l'accès implicite aux propriétés de sous-type, ce dont vous auriez besoin ici, mais apparemment Spring-Data ne peut pas gérer cela correctement. Je peux vous recommander de jeter un œil à Blaze-Persistence Entity-Views , une bibliothèque qui fonctionne au-dessus de JPA qui vous permet de mapper des structures arbitraires sur votre modèle d'entité. Vous pouvez mapper votre modèle DTO d'une manière sûre de type, également la structure d'héritage. Les vues d'entité pour votre cas d'utilisation pourraient ressembler à ceciAvec l'intégration des données de printemps fournie par Blaze-Persistence, vous pouvez définir un référentiel comme celui-ci et utiliser directement le résultat
Il générera une requête HQL qui sélectionne exactement ce que vous avez mappé dans ce
AnswerDTO
qui ressemble à ce qui suit.la source
interface MCValueDTO extends ValueDTO { @Mapping("selected.id") Set<Long> getOption(); }
Mon dernier projet a utilisé GraphQL (une première pour moi) et nous avons eu un gros problème avec les requêtes N + 1 et essayer d'optimiser les requêtes pour qu'elles ne se joignent aux tables que lorsqu'elles sont requises. J'ai trouvé irremplaçable Cosium / spring-data-jpa-entity-graph . Il étend
JpaRepository
et ajoute des méthodes pour passer un graphe d'entité à la requête. Vous pouvez ensuite créer des graphiques d'entités dynamiques au moment de l'exécution pour ajouter des jointures gauches uniquement aux données dont vous avez besoin.Notre flux de données ressemble à ceci:
Pour résoudre le problème de l'inclusion de nœuds non valides dans le graphe d'entité (par exemple à
__typename
partir de graphql), j'ai créé une classe utilitaire qui gère la génération du graphe d'entité. La classe appelante transmet le nom de la classe pour laquelle elle génère le graphique, qui valide ensuite chaque nœud du graphique par rapport au métamodèle géré par l'ORM. Si le nœud n'est pas dans le modèle, il le supprime de la liste des nœuds de graphe. (Cette vérification doit être récursive et vérifier également chaque enfant)Avant de trouver cela, j'avais essayé les projections et toutes les autres alternatives recommandées dans les documents Spring JPA / Hibernate, mais rien ne semblait résoudre le problème avec élégance ou au moins avec une tonne de code supplémentaire.
la source
selected
association n'est pas disponible pour tous les sous-types devalue
.Modifié après votre commentaire:
Je m'excuse, je n'ai pas sous-entendu votre problème au premier tour, votre problème se produit au démarrage de spring-data, pas seulement lorsque vous essayez d'appeler findAll ().
Ainsi, vous pouvez maintenant parcourir l'exemple complet qui peut être extrait de mon github: https://github.com/bdzzaid/stackoverflow-java/blob/master/jpa-hibernate/
Vous pouvez facilement reproduire et résoudre votre problème dans ce projet.
En effet, les données Spring et hibernate ne sont pas capables de déterminer le graphique "sélectionné" par défaut et vous devez spécifier la manière de collecter l'option sélectionnée.
Donc, tout d'abord, vous devez déclarer les NamedEntityGraphs de la classe Answer
Comme vous pouvez le voir, il existe deux NamedEntityGraph pour la valeur d' attribut de la classe Answer
Le premier pour tous Valeur sans relation spécifique avec la charge
Le second pour la valeur Multichoice spécifique . Si vous supprimez celui-ci, vous reproduisez l'exception.
Deuxièmement, vous devez être dans un contexte transactionnel answerRepository.findAll () si vous souhaitez récupérer des données de type LAZY
la source
value
association-deAnswer
mais d'obtenir l'selected
association au cas où avalue
estMCValue
. Votre réponse ne contient aucune information à ce sujet.OneToMany
commeFetchType.EAGER
mais comme indiqué dans la question: toutes les associations le sontLAZY
.selected
pour chaque réponse au lieu de les charger d'avance.