【问题标题】:Hibernate Criteria returns children multiple times with FetchType.EAGERHibernate Criteria 使用 FetchType.EAGER 多次返回子级
【发布时间】:2010-12-31 23:48:36
【问题描述】:

我有一个 Order 类,它有一个 OrderTransactions 列表,我使用一对多 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;
}

我必须如何更改我的条件代码才能在新设置下达到相同的结果?

【问题讨论】:

  • 您是否尝试过启用 show_sql 以查看下面发生了什么?
  • 请同时添加 OrderTransaction 和 Order 类代码。\

标签: java hibernate


【解决方案1】:

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

您在任何结果中都会得到相同的 Order 实例,但是由于现在您正在与 OrderTransaction 进行联接,因此它必须返回与常规 sql 联接返回的相同数量的结果

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


在 Hibernate FAQ 中也提到:

Hibernate 不会为集合启用外连接获取的查询返回不同的结果(即使我使用 distinct 关键词)?首先,您需要了解 SQL 以及 OUTER JOIN 的工作原理 在 SQL 中。如果你不完全理解和理解外连接 SQL,不要继续阅读此常见问题解答项目,而是查阅 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 个元素,都是 Order 类型。只有 5 个 Order 实例将 由 Hibernate 创建,但 SQL 结果集的副本是 保留作为对这 5 个实例的重复引用。如果你不 理解最后一句话,你需要阅读 Java 和 Java 堆上的实例和对的引用之间的区别 这样的例子。

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

默认情况下,Hibernate 不会过滤掉这些重复的引用。 有些人(不是你)实际上想要这个。如何过滤掉它们?

像这样:

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

【讨论】:

  • 即使你明白下面的解释,你也可能会在 Hibernate 论坛上抱怨这种行为,因为它是在翻转愚蠢的行为!
  • 非常正确的汤姆,我忘记了加文金斯傲慢的态度。他还说“默认情况下,Hibernate 不会过滤掉这些重复的引用。有些人(不是你)实际上想要这个'当人们真正反对这个时,我会感兴趣。
  • @TomAnderson 是的。 为什么会有人需要这些副本?我只是出于好奇而问,因为我不知道...您可以自己创建副本,任意数量.. ;-)跨度>
  • 叹息。这实际上是休眠缺陷,恕我直言。我想优化我的查询,所以我在映射文件中从“选择”转到“加入”。突然我的代码到处都是。然后我跑来跑去,通过附加结果转换器和诸如此类的东西来修复我所有的 DAO。用户体验 == 非常消极。我知道有些人出于奇怪的原因绝对喜欢重复,但为什么我不能通过指定 fetch="justworkplease" 说“更快地获取这些对象,但不要因为重复而困扰我”?
  • @Eran :我面临着类似的问题。我没有得到重复的父对象,但是我让每个父对象中的子对象重复的次数与响应中父对象的数量一样多。知道为什么会出现这个问题吗?
【解决方案2】:

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

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

【讨论】:

  • 这适用于大多数情况....除非您尝试使用 Criteria 获取 2 个集合/关联。
【解决方案3】:

试试

@Fetch (FetchMode.SELECT) 

例如

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

}

【讨论】:

  • FetchMode.SELECT 增加了 Hibernate 触发的 SQL 查询的数量,但确保每个根实体记录只有一个实例。在这种情况下,Hibernate 将为每个子记录触发一个选择。因此,您应该考虑到性能方面的考虑。
  • @BipulKumar 是的,但是当我们不能使用惰性获取时,这是一个选项,因为我们需要维护一个用于访问子对象的惰性获取会话。
【解决方案4】:

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

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

【讨论】:

  • 这是偶然提及的 Hibernate 最佳实践还是与 OP 的多子检索问题相关?
  • 知道了。次要于OP的问题。不过,根据作者自己在 cmets 中的承认,可能应该对 dzone 文章持保留态度。
  • 这是一个非常好的答案 IMO。如果您不想要重复,很可能您宁愿使用 Set 而不是 List - 使用 Set(当然还实现正确的 equals / hascode 方法)为我解决了这个问题。如redhat doc中所述,在实现hashcode / equals时请注意,不要使用id字段。
  • 感谢您的海事组织。此外,不要在创建 equals() 和 hashCode() 方法时遇到麻烦。让您的 IDE 或 Lombok 为您生成它们。
【解决方案5】:

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

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

Streams 非常快地删除重复项。我在我的实体类中使用这样的注释:

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

我认为在我的应用程序中可以在我需要来自数据库的数据的方法中使用会话。完成后关闭会话。当然,将我的实体类设置为使用 leasy fetch 类型。我去重构。

【讨论】:

    【解决方案6】:

    我在获取 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. 使用仅包含字段“meal”和类型 LOAD 的 EntityGraph,它们会在声明时加载角色(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);
    

    【讨论】:

      【解决方案7】:

      而不是使用以下技巧:

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

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

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

      它确实修改了生成的 sql 查询,因此其中有一个 DISTINCT

      【讨论】:

        【解决方案8】:

        应用外连接并带来重复的结果听起来不是一个很好的行为。剩下的唯一解决方案是使用流过滤我们的结果。感谢 java8 提供了更简单的过滤方式。

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

        【讨论】:

          猜你喜欢
          • 2014-06-01
          • 1970-01-01
          • 2013-11-09
          • 2013-06-25
          • 1970-01-01
          • 2018-06-03
          • 2012-09-15
          • 2012-08-10
          • 1970-01-01
          相关资源
          最近更新 更多