Hibernate Criteria使用FetchType.EAGER多次返回子项

时间:2010-01-03 14:11:49

标签: java hibernate

我有一个Order类,其列表为OrderTransactions,我将其映射为一对多的Hibernate映射,如下所示:

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

这些Order也有一个字段orderStatus,用于使用以下条件进行过滤:

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

这样可行,结果与预期一致。

现在这是我的问题:为什么,当我将fetch类型明确设置为EAGER时,Order在结果列表中多次出现?

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

我如何更改条件代码以使用新设置达到相同的结果?

8 个答案:

答案 0 :(得分:111)

如果我正确理解您的配置,这实际上是预期的行为。

您在任何结果中获得相同的Order实例,但从现在开始与OrderTransaction进行连接,它必须返回相同数量的结果,而常规sql join将返回

所以实际上它应该多次 apear。作者(Gavin King)本人here对此进行了很好的解释: 它既解释了原因,又解释了如何获得截然不同的结果

<小时/> 在Hibernate FAQ中也提到了:

  

对于为集合启用了外部联接提取的查询,Hibernate不会返回不同的结果(即使我使用了distinct   关键词)?首先,您需要了解SQL以及OUTER JOIN的工作方式   在SQL中。如果你没有完全理解和理解外部联接   SQL,不要继续阅读此FAQ项目,但请参阅SQL手册或   教程。否则您将无法理解以下说明   你会在Hibernate论坛上抱怨这种行为。

     

可能返回相同引用的重复引用的典型示例   订单对象:

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();
     

所有这些示例都生成相同的SQL语句:

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

想知道重复的原因在哪里?查看SQL结果集,   Hibernate不会在外部左侧隐藏这些重复项   加入结果但返回驱动表的所有重复项。如果   您在数据库中有5个订单,每个订单有3个订单项,   结果集将是15行。这些查询的Java结果列表   将有15个元素,所有类型顺序。只有5个订单实例   由Hibernate创建,但SQL结果集的副本是   保留为对这5个实例的重复引用。如果你不   理解这最后一句话,你需要阅读Java和   Java堆上的实例与对它的引用之间的区别   这样的例子。

     

(为什么左外连接?如果你有一个没有线的额外订单   项目,结果集将是16行,其中NULL填充右侧   其中,订单项数据用于其他订单。你想要订单   即使他们没有订单项,对吧?如果没有,请使用内部联接   获取你的HQL)。

     

默认情况下,Hibernate不会过滤掉这些重复的引用。   有些人(不是你)真的想要这个。你怎么能过滤掉它们?

     

像这样:

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

答案 1 :(得分:90)

除了Eran所提到的,另一种获得所需行为的方法是设置结果转换器:

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

答案 2 :(得分:41)

@Fetch (FetchMode.SELECT) 

例如

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

}

答案 3 :(得分:18)

不要使用List和ArrayList,而是使用Set和HashSet。

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

答案 4 :(得分:3)

使用Java 8和Streams我在实用程序方法中添加了这个返回语句:

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

Streams删除重复非常快。我在我的Entity类中使用注释,如下所示:

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

我认为在我的应用程序中使用会话方法,我需要来自数据库的数据。我完成的Closse会议。 Ofcourse设置我的Entity类使用leasy fetch类型。我去重构。

答案 5 :(得分:1)

我有同样的问题来获取2个相关的集合:用户有2个角色(Set)和2个饭菜(List)和饭菜是重复的。

@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. Change List to LinkedHashSet
  2. 仅使用EntityGraph字段&#34; meal&#34;并键入LOAD,它在声明时加载角色(EAGER和BatchSize = 200以防止N + 1问题):
  3. 最终解决方案:

    @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

    此类角色也被提取。

答案 6 :(得分:1)

而不是使用像这样的黑客:

  • Set而非List
  • criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

这不会修改您的sql查询,我们可以使用(引用JPA规范)

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

确实会修改生成的sql查询,因此其中包含DISTINCT

答案 7 :(得分:0)

应用外部联接并带来重复的结果听起来并不好。剩下的唯一解决方案是使用流过滤结果。感谢java8提供了更简单的过滤方法。

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