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.

3voto

GKislin Points 254

J'ai le même problème pour récupérer 2 collections associées : l'utilisateur a 2 rôles (Set) et 2 repas (List) et les repas sont dupliqués.

@Table(name = "users")
public class User extends AbstractNamedEntity {

   @CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id"))
   @Column(name = "role")
   @ElementCollection(fetch = FetchType.EAGER)
   @BatchSize(size = 200)
   private Set<Role> roles;

   @OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
   @OrderBy("dateTime DESC")
   protected List<Meal> meals;
   ...
}

DISTINCT n'aide pas (requête DATA-JPA) :

@EntityGraph(attributePaths={"meals", "roles"})
@QueryHints({@QueryHint(name= org.hibernate.jpa.QueryHints.HINT_PASS_DISTINCT_THROUGH, value = "false")}) // remove unnecessary distinct from select
@Query("SELECT DISTINCT u FROM User u WHERE u.id=?1")
User getWithMeals(int id);

J'ai enfin trouvé 2 solutions :

  1. Changer la liste en LinkedHashSet
  2. Utiliser EntityGraph avec seulement le champ "repas" et le type LOAD, qui charge les rôles tels qu'ils sont déclarés (EAGER et par BatchSize=200 pour éviter le problème N+1) :

Solution finale :

@EntityGraph(attributePaths = {"meals"}, type = EntityGraph.EntityGraphType.LOAD)
@Query("SELECT u FROM User u WHERE u.id=?1")
User getWithMeals(int id);

1voto

Kaustubh Points 77

Au lieu d'utiliser des astuces comme :

  • Set au lieu de List
  • criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

qui ne modifie pas votre requête sql, nous pouvons utiliser (en citant les spécifications JPA)

q.select(emp).distinct(true);

ce qui modifie la requête sql résultante, ayant ainsi une DISTINCT en elle.

0voto

Pravin Points 71

Cela ne semble pas être un bon comportement d'appliquer une jointure externe et d'apporter des résultats dupliqués. La seule solution qui reste est de filtrer nos résultats en utilisant des flux. Merci à java8 de nous donner un moyen plus facile de filtrer.

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

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