web-dev-qa-db-ja.com

Hibernate CriteriaはFetchType.EAGERで子を複数回返します

Orderのリストを持つOrderTransactionsクラスがあり、次のような1対多のHibernateマッピングでマッピングしました。

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

これらのOrdersには、フィールドorderStatusもあります。これは、次の基準でフィルタリングするために使用されます。

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

これは機能し、結果は期待どおりです。

さあ私の質問です:フェッチタイプを明示的にEAGERに設定すると、Ordersが結果リストに複数回表示されるのはなぜですか?

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

新しい設定で同じ結果を得るには、どのように基準コードを変更する必要がありますか?

111
raoulsson

Eranが述べていることに加えて、あなたが望む振る舞いを得る別の方法は、結果トランスフォーマーを設定することです:

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
90
EJB

試してみる

@Fetch (FetchMode.SELECT) 

例えば

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

}

41
mathi

ListとArrayListを使用せず、SetとHashSetを使用してください。

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

Java 8とStreamsをユーティリティメソッドに追加すると、この戻り値が返されます。

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

ストリームは重複を非常に高速に削除します。次のように、Entityクラスで注釈を使用します。

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

データベースのデータが必要なメソッドでセッションを使用することは、私のアプリではさらに進んでいると思います。完了したらセッションを終了します。もちろんEntityクラスは、遅延フェッチタイプを使用するように設定します。リファクタリングに行きます。

3
easyScript

2つの関連コレクションを取得するのと同じ問題があります。ユーザーには2つのロール(セット)と2つの食事(リスト)があり、食事が重複しています。

@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は役に立ちません(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);

ようやく2つのソリューションが見つかりました。

  1. LinkedHashSetにリストを変更
  2. EntityGraphをフィールド「meal」のみで使用し、タイプLOADを使用して、ロールを宣言どおりにロードします(EAGERおよびBatchSize = 200によりN + 1の問題を防止します)。

最終的解決:

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

更新:org.springframework.data.jpa.repository.EntityGraph.EntityGraphType#FETCHのjavadocを除く

エンティティグラフの属性ノードで指定された属性はFetchType.EAGERとして扱われ、指定されていない属性はFetchType.LAZYとして扱われます

このタイプのロールでもフェッチされます。

2
GKislin

外部結合を適用し、重複した結果をもたらす素晴らしい動作ではないようです。残っている唯一の解決策は、ストリームを使用して結果をフィルタリングすることです。簡単なフィルタリング方法を提供してくれたJava8に感謝します。

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