122 votes

Hibernate Criteria renvoie les enfants plusieurs fois avec FetchType.EAGER

J'ai un Order qui possède une liste de OrderTransactions et je l'ai mappé avec un mappage Hibernate de un à plusieurs comme ceci :

@OneToMany(targetEntity = OrderTransaction.class, cascade = CascadeType.ALL)
public List<OrderTransaction> getOrderTransactions() {
    return orderTransactions;
}

Ces Order ont également un champ orderStatus qui est utilisé pour le filtrage avec les critères suivants :

public List<Order> getOrderForProduct(OrderFilter orderFilter) {
    Criteria criteria = getHibernateSession()
            .createCriteria(Order.class)
            .add(Restrictions.in("orderStatus", orderFilter.getStatusesToShow()));
    return criteria.list();
}

Cela fonctionne et le résultat est conforme aux attentes.

Maintenant voici ma question : Pourquoi, lorsque je définis explicitement le type de récupération à EAGER faites le Order apparaissent plusieurs fois dans la liste résultante ?

@OneToMany(targetEntity = OrderTransaction.class, fetch = FetchType.EAGER, cascade = CascadeType.ALL)
public List<OrderTransaction> getOrderTransactions() {
    return orderTransactions;
}

Comment devrais-je modifier mon code de critères pour obtenir le même résultat avec le nouveau paramètre ?

1 votes

Avez-vous essayé d'activer show_sql pour voir ce qui se passe en dessous ?

0 votes

Veuillez ajouter le code des classes OrderTransaction et Order également.

125voto

Eran Medan Points 12234

C'est en fait le comportement attendu si j'ai bien compris votre configuration.

Vous obtenez le même Order dans l'un des résultats, mais puisque vous effectuez maintenant une jointure avec le fichier OrderTransaction elle doit retourner le même nombre de résultats qu'une jointure sql normale.

Donc en fait, il devrait Apparaissent plusieurs fois. L'auteur (Gavin King) lui-même l'explique très bien. aquí : Il explique à la fois pourquoi et comment obtenir des résultats distincts.


Également mentionné dans la [FAQ][2] d'Hibernate :

Hibernate ne renvoie pas de résultats distincts pour une requête dont la récupération par jointure externe est activée pour une collection. (même si j'utilise l'option distincte ) ? Tout d'abord, vous devez comprendre le langage SQL et le fonctionnement des jointures OUTER en SQL. Si vous ne comprenez pas entièrement les jointures externes en SQL, ne continuez pas à lire cette FAQ. SQL, ne poursuivez pas la lecture de cette FAQ mais consultez un manuel ou un tutoriel tutoriel SQL. Sinon, vous ne comprendrez pas l'explication suivante et vous vous plaindrez de ce comportement sur le forum Hibernate.

Des exemples typiques qui peuvent renvoyer des références dupliquées d'un même objet Ordre :

List result = session.createCriteria(Order.class)
                    .setFetchMode("lineItems", FetchMode.JOIN)
                    .list();

<class name="Order">
    ...
    <set name="lineItems" fetch="join">

List result = session.createCriteria(Order.class)
                       .list();
List result = session.createQuery("select o from Order o left join fetch o.lineItems").list();

Tous ces exemples produisent la même instruction SQL :

SELECT o.*, l.* from ORDER o LEFT OUTER JOIN LINE_ITEMS l ON o.ID = l.ORDER_ID

Vous voulez savoir pourquoi il y a des doublons ? Regardez le jeu de résultats SQL, Hibernate ne cache pas ces doublons sur le côté gauche du résultat extérieur joint. mais renvoie tous les doublons de la table principale. Si vous avez 5 commandes dans la base de données, et chaque commande a 3 articles de ligne, le jeu de résultats sera de 15 lignes. La liste de résultats Java de ces requêtes comportera 15 éléments, tous de type Order. Seules 5 instances de commande seront seront créées par Hibernate, mais les doublons du jeu de résultats SQL sont sont préservés en tant que références doubles à ces 5 instances. Si vous ne comprenez pas Si vous ne comprenez pas cette dernière phrase, vous devez vous documenter sur Java et sur la différence entre une instance sur le serveur Java et une instance sur le serveur SQL. Java et la différence entre une instance sur le tas Java et une référence à une telle instance.

(Pourquoi une jointure externe gauche ? Si vous aviez une commande supplémentaire sans article, le résultat serait de 16 lignes avec NULL à droite. articles, le jeu de résultats serait de 16 lignes avec NULL remplissant la partie droite où les données des postes sont pour l'autre commande. Vous voulez des commandes même si elles n'ont pas de postes, n'est-ce pas ? Si non, utilisez une jointure interne fetch dans votre HQL).

Hibernate ne filtre pas ces références dupliquées par défaut. Certaines personnes (pas vous) le souhaitent. Comment pouvez-vous les filtrer ?

Comme ça :

Collection result = new LinkedHashSet( session.create*(...).list() );

127 votes

Même si vous comprenez l'explication suivante, vous pourriez bien vous plaindre de ce comportement sur le forum Hibernate, car c'est un comportement tout à fait stupide !

18 votes

C'est vrai Tom, j'avais oublié l'attitude arrogante de Gavin Kings. Il dit également que "Hibernate ne filtre pas ces références dupliquées par défaut. Certaines personnes (pas vous) le souhaitent réellement". Je suis intéressé par le fait que les gens le souhaitent réellement.

17 votes

@TomAnderson oui exactement. pourquoi quelqu'un aurait-il besoin de ces doublons ? Je vous le demande par pure curiosité, car je n'en ai aucune idée... Vous pouvez créer vous-même des duplicatas, autant que vous le souhaitez... ;-)

93voto

EJB Points 1192

En plus de ce qui est mentionné par Eran, une autre façon d'obtenir le comportement que vous voulez, est de définir le transformateur de résultat :

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

8 votes

Cela fonctionnera dans la plupart des cas.... sauf lorsque vous essayez d'utiliser des critères pour récupérer 2 collections/associations.

42voto

mathi Points 176

Essayez

@Fetch (FetchMode.SELECT) 

par exemple

@OneToMany(targetEntity = OrderTransaction.class, fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@Fetch (FetchMode.SELECT)
public List<OrderTransaction> getOrderTransactions() {
return orderTransactions;

}

11 votes

FetchMode.SELECT augmente le nombre de requêtes SQL lancées par Hibernate mais garantit une seule instance par enregistrement d'entité racine. Dans ce cas, Hibernate lancera une sélection pour chaque enregistrement enfant. Vous devez donc en tenir compte dans les considérations relatives aux performances.

1 votes

@BipulKumar oui, mais c'est l'option lorsque nous ne pouvons pas utiliser la récupération paresseuse parce que nous devons maintenir une session pour la récupération paresseuse pour accéder aux sous-objets.

20voto

N'utilisez pas List et ArrayList mais Set et HashSet.

@OneToMany(targetEntity = OrderTransaction.class, cascade = CascadeType.ALL)
public Set<OrderTransaction> getOrderTransactions() {
    return orderTransactions;
}

2 votes

S'agit-il d'une mention fortuite d'une meilleure pratique d'Hibernate ou d'un élément pertinent pour la question de la récupération multi-enfants du PO ?

0 votes

Je l'ai. Secondaire à la question de l'OP. Cependant, l'article de la Dzone devrait probablement être pris avec un grain de sel... basé sur l'admission de l'auteur lui-même dans les commentaires.

3voto

easyScript Points 153

En utilisant Java 8 et Streams, j'ajoute dans ma méthode utilitaire cette déclaration de retour :

return results.stream().distinct().collect(Collectors.toList());

Les flux suppriment les doublons très rapidement. J'utilise une annotation dans ma classe Entity comme ceci :

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinTable(name = "STUDENT_COURSES")
private List<Course> courses;

Je pense qu'il est préférable dans mon application d'utiliser la session dans la méthode où j'ai besoin de données de la base de données. Fermer la session lorsque j'ai terminé. Bien sûr, je configure ma classe d'entité pour utiliser le type de récupération facile. Je vais au refactor.

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