2009-02-26 7 views
13

J'ai la structure d'entité suivante: Business -> Campaign -> Promotion, où ONE Business peut avoir BEAUCOUP de campagnes et ONE Campaign peut avoir BEAUCOUP de promotions. Les deux relations un-à-plusieurs sont déclarées comme LAZY. Un endroit dans mon code, je dois aller chercher avec impatience les collections d'une entreprise, donc je fais:JPA: aidez-nous à comprendre "join fetch"

Query query = entityManager.createQuery("select b from Business b " + 
      "left join fetch b.campaigns c " + 
      "left join fetch c.promotions where b.id=:id"); 
query.setParameter("id", b.getId()); 
business = (Business) query.getResultList().get(0); 

Cependant, la requête renvoie une liste de résultats qui contient 4 objets métier en elle, tous les 4 objets font référence à la même instance Business. Dans ma base de données, cette entreprise compte trois campagnes et toutes les trois campagnes ont trois promotions.

J'ai deux questions:

  1. Au début, j'utiliser la liste pour contient les multiples facettes des relations, mais lorsque le programme court, je reçois « org.hibernate.HibernateException: ne peut pas chercher simultanément plusieurs sacs "exception. Ensuite, j'ai googlé cette exception et il semble que je doive utiliser Set, au lieu de List. J'ai donc changé la collection pour Set et cela a fonctionné. Quelqu'un peut-il me dire pourquoi la liste ne fonctionnera pas dans cette situation?

  2. Je m'attends à ce que la requête renvoie un résultat unique, car elle interroge l'ID, qui est la clé primaire et ne devrait donc renvoyer qu'un seul résultat. Mais il s'avère qu'il renvoie 4 instances dans une liste. Est-ce un problème? Ou est ce comportement attendu?

Toute aide sera grandement appréciée.

Répondre

20

Le sql généré ressemblerait à quelque chose comme:

select * from Business b 
left outer join campaigns c on c.business_id = b.id 
left join promotions p on p.campaign_id = c.id 
where b.id=:id 

interne Hibernate n'aura qu'une seule instance Business, mais les doublons seront conservés dans le jeu de résultats. C'est un comportement attendu. Le comportement dont vous avez besoin peut être acheived soit en utilisant la clause DISTINCT, ou en utilisant un LinkedHashSet pour filtrer les résultats:

Collection result = new LinkedHashSet(query.getResultList()); 

qui renverra que des résultats uniques, préserver l'ordre d'insertion. La "org.hibernate.HibernateException: impossible d'extraire plusieurs sacs simultanément" se produit chaque fois que vous essayez d'aller chercher plus d'une collection de manière ordonnée (et éventuellement des éléments dupliqués). Cela a un sens si vous considérez le SQL généré. Hibernate n'a aucun moyen de savoir si un objet dupliqué a été causé par la jointure ou par des données dupliquées réelles dans la table enfant. Regardez this pour une bonne explication.

+1

L'ajout de "distinct" à la requête a fait l'affaire. Merci. –

1
  1. Je suppose que si vous utilisez une liste, mise en veille prolongée suppose que l'ordre est important, et il déduira l'ordre des éléments des lignes retournées, mais si vous vous joignez à une mise en veille prolongée table utre obtiendra chaque multiple de campagne fois qui confond hiberner. Encore une fois, je suis en train de prendre cette

  2. Ce comportement est normal Utilisez un RootEntityResultTransformer pour obtenir une seule entité racine: http://www.hibernate.org/hib_docs/v3/api/org/hibernate/transform/RootEntityResultTransformer.html

3
  1. Liste peut avoir des éléments redondants en raison du produit cartésien et les gens étaient pas au courant de cela, de sorte que les gars de mise en veille prolongée a commencé à jeter cette erreur pour forcer les gens à utiliser Set au lieu de la liste pour éviter ce problème. Source 1
0

Une autre solution consiste à utiliser deux requêtes au lieu d'utiliser Set pour obtenir les données: - La première consiste à charger la campagne et les promotions associées de l'entreprise. Ensuite, chargez l'activité et ses campagnes à l'aide de la fonction de récupération

Query query1 = entityManager.createQuery("select c from Business b join b.campaigns c left join fetch c.promotions where b.id=:id"); 
    query1.setParameter("id", b.getId()); 
    query1.getResultList(); 

    Query query2 = entityManager.createQuery("select b from Business b left join fetch  b.campaigns where b.id=:id"); 
    query2.setParameter("id", b.getId()); 
    business = (Business) query2.getResultList().get(0);