107 votes

JPA eager fetch ne joint pas

Que contrôle exactement la stratégie fetch de JPA ? Je ne vois pas de différence entre les stratégies "eager" et "lazy". Dans les deux cas, JPA/Hibernate ne joint pas automatiquement les relations many-to-one.

Exemple : Une personne a une seule adresse. Une adresse peut appartenir à plusieurs personnes. Les classes d'entités annotées JPA ressemblent à ceci :

@Entity
public class Person {
    @Id
    public Integer id;

    public String name;

    @ManyToOne(fetch=FetchType.LAZY or EAGER)
    public Address address;
}

@Entity
public class Address {
    @Id
    public Integer id;

    public String name;
}

Si j'utilise la requête JPA :

select p from Person p where ...

JPA/Hibernate génère une requête SQL pour sélectionner dans la table Person, puis une requête d'adresse distincte pour chaque personne :

select ... from Person where ...
select ... from Address where id=1
select ... from Address where id=2
select ... from Address where id=3

C'est très mauvais pour les grands ensembles de résultats. S'il y a 1000 personnes, cela génère 1001 requêtes (1 de Personne et 1000 distinctes d'Adresse). Je le sais parce que je regarde le journal des requêtes de MySQL. J'ai cru comprendre qu'en définissant le type de récupération de l'adresse sur eager, JPA/Hibernate effectuera automatiquement une requête avec une jointure. Cependant, quel que soit le type de recherche, il génère toujours des requêtes distinctes pour les relations.

Ce n'est que lorsque je lui demande explicitement de se joindre à moi qu'il le fait :

select p, a from Person p left join p.address a where ...

Est-ce que je rate quelque chose ? Je dois maintenant coder manuellement chaque requête pour qu'elle joigne à gauche les relations plusieurs à un. J'utilise l'implémentation JPA d'Hibernate avec MySQL.

Edit : Il semble (voir la FAQ Hibernate ici y ici ) que FetchType n'a pas d'incidence sur les requêtes JPA. Dans mon cas, je dois donc lui dire explicitement de se joindre.

93voto

Yadu Points 919

JPA ne fournit aucune spécification sur le mappage des annotations pour sélectionner la stratégie de récupération. En général, les entités liées peuvent être récupérées de l'une des manières suivantes

  • SELECT => une requête pour les entités racine + une requête pour les entités cartographiées liées/collection de chaque entité racine = (n+1) requêtes
  • SUBSELECT => une requête pour les entités racine + une deuxième requête pour les entités mappées associées/collection de toutes les entités racine récupérées dans la première requête = 2 requêtes.
  • JOIN => une seule requête pour récupérer les deux entités Root et toutes leurs entités/collections mappées = 1 requête

Así que SELECT y JOIN sont deux extrêmes et SUBSELECT se situe entre les deux. On peut choisir la stratégie appropriée en fonction de son modèle de domaine.

Par défaut SELECT est utilisé à la fois par JPA/EclipseLink et Hibernate. Ceci peut être surchargé en utilisant :

@Fetch(FetchMode.JOIN) 
@Fetch(FetchMode.SUBSELECT)

dans Hibernate. Il permet également de définir SELECT en utilisant explicitement le mode @Fetch(FetchMode.SELECT) qui peut être ajustée en utilisant la taille du lot, par exemple. @BatchSize(size=10) .

Les annotations correspondantes dans EclipseLink sont :

@JoinFetch
@BatchFetch

44voto

rudolfson Points 2527

"mxc" a raison. fetchType précise simplement quand la relation doit être résolue.

Pour optimiser le chargement empressé en utilisant une jointure externe, vous devez ajouter

@Fetch(FetchMode.JOIN)

dans votre domaine. Il s'agit d'une annotation spécifique à Hibernate.

38voto

mxc Points 538

L'attribut fetchType contrôle si le champ annoté est récupéré immédiatement lorsque l'entité primaire est récupérée. Il ne dicte pas nécessairement la façon dont l'instruction de récupération est construite, l'implémentation sql réelle dépend du fournisseur que vous utilisez (toplink/hibernate, etc.).

Si vous définissez fetchType=EAGER Cela signifie que le champ annoté est alimenté avec ses valeurs en même temps que les autres champs de l'entité. Ainsi, si vous ouvrez un gestionnaire d'entités pour récupérer vos objets person et que vous fermez ensuite le gestionnaire d'entités, l'exécution ultérieure de person.address n'entraînera pas l'apparition d'une exception de chargement paresseux.

Si vous définissez fetchType=LAZY le champ n'est alimenté que lorsqu'on y accède. Si vous avez fermé le gestionnaire d'entités à ce moment-là, une exception de chargement paresseux sera levée si vous faites un person.address. Pour charger le champ, vous devez remettre l'entité dans un contexte entitymangers avec em.merge(), puis effectuer l'accès au champ et enfin fermer le gestionnaire d'entités.

Vous pouvez vouloir un chargement paresseux lorsque vous construisez une classe client avec une collection pour les commandes des clients. Si vous récupérez chaque commande d'un client lorsque vous voulez obtenir une liste de clients, cela peut être une opération de base de données coûteuse alors que vous ne recherchez que le nom du client et ses coordonnées. Il est préférable de laisser l'accès à la base de données pour plus tard.

Pour la deuxième partie de la question - comment faire en sorte qu'hibernate génère un SQL optimisé ?

Hibernate devrait vous permettre de fournir des indications sur la manière de construire la requête la plus efficace, mais je pense qu'il y a un problème dans la construction de votre table. La relation est-elle établie dans les tables ? Hibernate a peut-être décidé qu'une simple requête sera plus rapide qu'une jointure, surtout si les index, etc. sont absents.

18voto

sinuhepop Points 7075

Essayez avec :

select p from Person p left join FETCH p.address a where...

Cela fonctionne pour moi de manière similaire avec JPA2/EclipseLink, mais il semble que cette fonctionnalité soit présente dans JPA1 aussi :

7voto

Si vous utilisez EclipseLink au lieu d'Hibernate, vous pouvez optimiser vos requêtes grâce aux "query hints". Voir cet article du Wiki Eclipse : Lien Eclipse/Exemples/JPA/QueryOptimisation .

Il y a un chapitre sur la "lecture jointe".

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X