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

rao*_*son 113 java hibernate

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

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

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

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

这有效,结果如预期.

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

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

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

Era*_*dan 113

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

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

实际上它应该多次出现.作者(Gavin King)自己在这里解释了这一点:它既解释了原因,又解释了如何获得截然不同的结果


在Hibernate FAQ中也提到过:

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

可能返回同一Order对象的重复引用的典型示例:

List result = session.createCriteria(Order.class)
                    .setFetchMode("lineItems", FetchMode.JOIN)
                    .list();
Run Code Online (Sandbox Code Playgroud)
<class name="Order">
    ...
    <set name="lineItems" fetch="join">
Run Code Online (Sandbox Code Playgroud)
List result = session.createCriteria(Order.class)
                       .list();
List result = session.createQuery("select o from Order o left join fetch o.lineItems").list();
Run Code Online (Sandbox Code Playgroud)

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

SELECT o.*, l.* from ORDER o LEFT OUTER JOIN LINE_ITEMS l ON o.ID = l.ORDER_ID
Run Code Online (Sandbox Code Playgroud)

想知道重复的原因在哪里?查看SQL结果集,Hibernate不会在外部联接结果的左侧隐藏这些重复项,但会返回驱动表的所有重复项.如果数据库中有5个订单,并且每个订单有3个订单项,则结果集将为15行.这些查询的Java结果列表将包含15个元素,所有元素都是Order.Hibernate只会创建5个Order实例,但SQL结果集的重复项将保留为对这5个实例的重复引用.如果您不理解最后一句,您需要阅读Java以及Java堆上的实例与对此类实例的引用之间的区别.

(为什么左外连接?如果你有一个没有行项目的附加订单,结果集将是16行,其中NULL填充右侧,其中行项目数据用于其他顺序.即使是它们没有行项目,对吧?如果没有,请在HQL中使用内部联接提取).

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

像这样:

Collection result = new LinkedHashSet( session.create*(...).list() );
Run Code Online (Sandbox Code Playgroud)

  • 即使您确实理解以下解释,您可能会在Hibernate论坛上抱怨这种行为,因为它正在翻转愚蠢的行为! (117认同)
  • 相当对的汤姆,我忘记了加文国王的傲慢态度.他还说'默认情况下,Hibernate不会过滤掉这些重复的引用.有些人(不是你)实际上想要这个'当人们真正反对这个时,我会感兴趣. (16认同)
  • @TomAnderson是的确切.**为什么有人会需要那些重复的东西?**我问的是纯粹的好奇心,因为我不知道......你可以自己创建副本,就像你想要的那样多... ;-) (16认同)
  • 叹.这实际上是Hibernate的缺陷,恕我直言.我想优化我的查询,所以我从映射文件中的"select"转到"join".突然间,我的代码突破了所有地方.然后我跑来跑去,通过附加结果变换器和诸如此类的东西来修复我的所有DAO.用户体验==非常消极.我知道有些人因为奇怪的原因绝对喜欢重复,但为什么我不能说"通过指定fetch ="justworkplease"更快地获取这些对象但不会让我感到重复"? (13认同)

EJB*_*EJB 92

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

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
Run Code Online (Sandbox Code Playgroud)

  • 这适用于大多数情况....除了当您尝试使用Criteria获取2个集合/关联时. (8认同)

小智 42

尝试

@Fetch (FetchMode.SELECT) 
Run Code Online (Sandbox Code Playgroud)

例如

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

}

  • FetchMode.SELECT增加了Hibernate触发的SQL查询的数量,但确保每个根实体记录只有一个实例.在这种情况下,Hibernate将为每个子记录触发一个选择.因此,您应该考虑性能因素. (11认同)

Αλέ*_*κος 18

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

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

  • 这是偶然提到的 Hibernate 最佳实践还是与 OP 中的多子检索问题相关? (2认同)
  • 这是IMO非常好的答案.如果你不想复制,很可能你宁愿使用Set而不是List--使用Set(当然实现正确的equals/hascode方法)解决了我的问题.在执行hashcode/equals时要小心,如redhat doc中所述,不要使用id字段. (2认同)