web-dev-qa-db-fra.com

Les critères Hibernate renvoient les enfants plusieurs fois avec FetchType.EAGER

J'ai une classe Order qui a une liste de OrderTransactions et je l'ai mappée avec un mappage Hibernate un à plusieurs comme suit:

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

Ces Orders ont également un champ orderStatus, qui est utilisé pour filtrer 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 comme prévu.

Maintenant voici ma question : Pourquoi, lorsque je mets explicitement le type de recherche à EAGER, la Orders apparaît-elle 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 changer mon code de critères pour atteindre le même résultat avec le nouveau paramètre?

106
raoulsson

En plus de ce qui est mentionné par Eran, un autre moyen d'obtenir le comportement souhaité est de définir le transformateur de résultat:

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
89
EJB

essayer

@Fetch (FetchMode.SELECT) 

par exemple

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

}

40
mathi

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

@OneToMany(targetEntity = OrderTransaction.class, cascade = CascadeType.ALL)
public Set<OrderTransaction> getOrderTransactions() {
    return orderTransactions;
}
17
Αλέκος

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

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

Les flux suppriment les doublons très rapidement. J'utilise des annotations dans ma classe d'entité comme ceci:

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

Je pense qu'il est temps dans mon application d'utiliser la session dans la méthode où j'ai besoin de données de base de données. Closse session quand j'ai fini. Ofcourse a défini ma classe Entity pour utiliser le type d'extraction leasy. Je vais au refactor.

3
easyScript

J'ai le même problème pour récupérer 2 collections associées: l'utilisateur a 2 rôles (Ensemble) et 2 repas (Liste) 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);

Enfin, j'ai trouvé 2 solutions:

  1. Changer la liste en LinkedHashSet
  2. Utilisez EntityGraph avec uniquement le champ "meal" et tapez LOAD, qui charge les rôles tels qu'ils ont été déclarés (EAGER et par BatchSize = 200 pour éviter tout 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);

UPDATE: sauf si javadoc pour org.springframework.data.jpa.repository.EntityGraph.EntityGraphType#FETCH

les attributs spécifiés par les nœuds d'attribut du graphique d'entité sont traités comme FetchType.EAGER et les attributs non spécifiés sont traités comme FetchType.LAZY

avec ce type, les rôles sont également récupérés.

1
GKislin

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

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