web-dev-qa-db-fra.com

JPA désireux chercher ne rejoint pas

En quoi consiste exactement le contrôle de la stratégie de récupération de JPA? Je ne peux déceler aucune différence entre désireux et paresseux. Dans les deux cas, JPA/Hibernate ne joint pas automatiquement les relations plusieurs à un.

Exemple: une personne a une seule adresse. Une adresse peut appartenir à plusieurs personnes. Les classes d'entités annotées JPA se présentent comme suit:

@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 à sélectionner dans la table Personne, puis une requête 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, il génère 1001 requêtes (1 de la personne et 1000 distinctes de l'adresse). Je le sais parce que je regarde le journal des requêtes de MySQL. Je croyais comprendre que la définition du type de recherche d'adresse sur "eager" ferait en sorte que JPA/Hibernate interrogerait automatiquement une jointure. Cependant, quel que soit le type d'extraction, il génère toujours des requêtes distinctes pour les relations.

Ce n'est que lorsque je le dis explicitement à rejoindre qu'il rejoint réellement:

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

Est-ce que j'ai râté quelque chose? Je dois maintenant coder chaque requête de sorte qu'elle reste jointe aux relations plusieurs-à-un. J'utilise l'implémentation JPA d'Hibernate avec MySQL.

Edit: Il apparaît (voir Hibernate FAQ ici et ici ) que FetchType n’a pas d’impact sur les requêtes JPA, donc dans mon cas, j’ai dit explicitement de le joindre.

103
Steve Kuo

JPA ne fournit aucune spécification sur les annotations de mappage pour sélectionner la stratégie d'extraction. En général, les entités liées peuvent être récupérées de n'importe laquelle des manières indiquées ci-dessous

  • SELECT => une requête pour les entités racine + une requête pour les entités mappées/collections associées de chaque entité racine = (n + 1) requêtes
  • SUBSELECT => une requête pour les entités racines + seconde requête pour les entités mappées/collections associées de toutes les entités racines extraites dans la première requête = 2 requêtes
  • JOIN => une requête pour extraire les deux entités racine et toutes leurs entités/collections mappées = 1 requête

Donc, SELECT et JOIN sont deux extrêmes et SUBSELECT se situe entre les deux. On peut choisir une 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 remplacé en utilisant:

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

à Hibernate. Cela permet également de définir le mode SELECT de manière explicite à l'aide de @Fetch(FetchMode.SELECT), qui peut être réglé à l'aide de la taille de lot, par exemple. @BatchSize(size=10).

Les annotations correspondantes dans EclipseLink sont les suivantes:

@JoinFetch
@BatchFetch
89
Yadu

"mxc" a raison. fetchType spécifie simplement when la relation doit être résolue.

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

@Fetch(FetchMode.JOIN)

à votre domaine. Ceci est une annotation spécifique à l'hibernation.

43
rudolfson

L'attribut fetchType contrôle si le champ annoté est extrait immédiatement lorsque l'entité principale est extraite. Cela ne dicte pas nécessairement la manière dont l'instruction fetch est construite, l'implémentation de SQL dépend du fournisseur que vous utilisez, toplink/hibernate, etc.

Si vous définissez fetchType=EAGER Cela signifie que le champ annoté est renseigné avec ses valeurs en même temps que les autres champs de l'entité. Par conséquent, si vous ouvrez un entitymanager, récupérez vos objets Personne, puis fermez ce dernier, puis créer une personne.adresse ne générera pas d'exception de chargement paresseux.

Si vous définissez fetchType=LAZY le champ n'est renseigné que lorsque vous y accédez. Si vous avez fermé le module entitymanager à ce moment-là, une exception de chargement paresseux sera levée si vous créez une personne.adresse. Pour charger le champ, vous devez replacer l'entité dans un contexte entitymangers avec em.merge (), puis accéder au champ, puis fermer le gestionnaire d'entités.

Vous voudrez peut-être un chargement paresseux lors de la construction d'une classe client avec une collection pour les commandes client. Si vous récupérez chaque commande pour un client lorsque vous souhaitez obtenir une liste de clients, l'opération de base de données peut s'avérer coûteuse si vous recherchez uniquement le nom du client et ses coordonnées. Le mieux est de laisser l’accès à la base de données plus tard.

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

Hibernate devrait vous permettre de donner des conseils sur la manière de construire la requête la plus efficace, mais je soupçonne qu’il ya un problème avec la construction de votre table. La relation est-elle établie dans les tables? Hibernate a peut-être décidé qu'une simple requête serait plus rapide qu'une jointure, en particulier si des index, etc. sont manquants.

36
mxc

Essayez avec:

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

Cela fonctionne pour moi dans un semblable avec JPA2/EclipseLink, mais il semble que cette fonctionnalité est présente dans JPA1 aussi :

18
sinuhepop

Si vous utilisez EclipseLink au lieu de Hibernate, vous pouvez optimiser vos requêtes par des "indications de requête". Voir cet article du wiki Eclipse: EclipseLink/Examples/JPA/QueryOptimization .

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

7

pour vous rejoindre, vous pouvez faire plusieurs choses (en utilisant eclipselink)

  • en jpql, vous pouvez faire une gauche rejoindre chercher

  • dans une requête nommée, vous pouvez spécifier un indice de requête

  • dans TypedQuery, vous pouvez dire quelque chose comme

    query.setHint("eclipselink.join-fetch", "e.projects.milestones");

  • il y a aussi un indice de recherche par lot

    query.setHint("eclipselink.batch", "e.address");

voir

http://Java-persistence-performance.blogspot.com/2010/08/batch-fetching-optimizing-object-graph.html

2
Kalpesh Soni

J'ai eu exactement ce problème à l'exception que la classe Personne avait une classe de clé intégrée. Ma propre solution consistait à les rejoindre dans la requête AND remove

@Fetch(FetchMode.JOIN)

Ma classe d'identifiant intégré:

@Embeddable
public class MessageRecipientId implements Serializable {

    @ManyToOne(targetEntity = Message.class, fetch = FetchType.LAZY)
    @JoinColumn(name="messageId")
    private Message message;
    private String governmentId;

    public MessageRecipientId() {
    }

    public Message getMessage() {
        return message;
    }

    public void setMessage(Message message) {
        this.message = message;
    }

    public String getGovernmentId() {
        return governmentId;
    }

    public void setGovernmentId(String governmentId) {
        this.governmentId = governmentId;
    }

    public MessageRecipientId(Message message, GovernmentId governmentId) {
        this.message = message;
        this.governmentId = governmentId.getValue();
    }

}
1
Gunslinger