一对多关系在不使用“distinct”的情况下获取重复的对象。为啥?

Posted

技术标签:

【中文标题】一对多关系在不使用“distinct”的情况下获取重复的对象。为啥?【英文标题】:One-To-Many relationship gets duplicate objects without using "distinct". Why?一对多关系在不使用“distinct”的情况下获取重复的对象。为什么? 【发布时间】:2013-09-16 04:32:37 【问题描述】:

我有 2 个具有一对多关系的类和一个有点奇怪的 HQL 查询。即使我已经阅读了一些已经发布的问题,我似乎也不清楚。

Class Department
   @OneToMany(fetch=FetchType.EAGER, mappedBy="department")
   Set<Employee> employees;

Class Employee
   @ManyToOne
   @JoinColumn(name="id_department")
   Department department;

当我使用以下查询时,我得到重复的部门对象:

session.createQuery("select dep from Department as dep left join dep.employees");

因此,我必须使用 distinct:

session.createQuery("select distinct dep from Department as dep left join dep.employees");

这种行为是预期的吗?与 SQL 相比,我认为这很不寻常。

【问题讨论】:

为什么要创建left join,只是简单的from Department,而且您的获取类型也很急切。 sql输出是什么? 实际上查询有一个 where 子句,我需要为每个员工设置一个条件,from Department 在这种情况下不会帮助我。 select dep from Department as dep left join dep.employees emp where emp.enddate &gt; current_date() 【参考方案1】:

这个问题在Hibernate FAQ上有详细解释:

首先,您需要了解 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">
    ...
</class>

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 个 订单实例将由 Hibernate 创建,但 SQL 结果集被保留为对这 5 个的重复引用 实例。如果你不明白这最后一句话,你需要 阅读 Java 和 Java 上的实例之间的区别 堆和对此类实例的引用。 (为什么是左外连接?如果 您将有一个没有订单项的附加订单,即结果集 将是 16 行,右侧填充 NULL,其中行 项目数据用于其他订单。你想要订单,即使他们没有 订单项,对吧?如果没有,请在您的 HQL 中使用内部连接提取)。 Hibernate 默认不会过滤掉这些重复的引用。 有些人(不是你)实际上想要这个。你怎么能过滤掉它们? 像这样:

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

LinkedHashSet 过滤掉重复的引用(它是一个集合)并且 它保留插入顺序(结果中元素的顺序)。那 太容易了,所以你可以做很多不同的,更难的 方式:

List result = session.createCriteria(Order.class)  
                        .setFetchMode("lineItems", FetchMode.JOIN)  
                        .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)  
                        .list();  


<class name="Order">  
    ...  
    <set name="lineItems" fetch="join">  

List result = session.createCriteria(Order.class)  
                        .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)  
                        .list();  

List result = session.createQuery("select o from Order o left join fetch o.lineItems")  
                      .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY) // Yes, really!  
                      .list();  

List result = session.createQuery("select distinct o from Order o left join fetch o.lineItems").list();       

最后一个很特别。看起来您正在使用 SQL DISTINCT 关键字在这里。当然,这不是 SQL,这是 HQL。这 在这种情况下, distinct 只是结果转换器的快捷方式。 是的,在其他情况下,不同的 HQL 将直接转换为 SQL 清楚的。不是在这种情况下:您不能在 SQL 级别,产品/连接的本质禁止这样做 - 你想要 重复或您无法获得所需的所有数据。所有这些 当结果集为 编组成对象。为什么结果集也应该很明显 基于行的“限制”操作,例如 setFirstResult(5) 和 setMaxResults(10) 不适用于这些急切的获取查询。 如果将结果集限制为一定数量的行,则切断 数据随机。有一天,Hibernate 可能会足够聪明地知道,如果 你调用 setFirstResult() 或 setMaxResults() 它不应该使用连接, 但是第二个 SQL SELECT。试试看,你的 Hibernate 版本可能 已经足够聪明了。如果不是,写两个查询,一个用于限制 东西,另一个是急切的。你想知道为什么 带有 Criteria 查询的示例没有忽略 fetch="join" 在映射中设置但 HQL 不在乎?阅读下一个常见问题解答项目。

【讨论】:

我遇到了性能问题。我在想也许 Hibernate 正在尝试为每个重复的项目创建 Java 对象,然后做一个不同的?所以基本上做很多不必要的对象创建会导致速度变慢,是这样吗?有没有更有效的解决方案?【参考方案2】:

使用结果转换器Criteria.DISTINCT_ROOT_ENTITY:

List result = session.createQuery("hql query")  
                        .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)  
                        .list();

【讨论】:

【参考方案3】:

这是我从 Vlad Mihalcea 先生那里学到的一个很棒的技巧。更多提示: https://vladmihalcea.com/tutorials/hibernate/

    list = session.createQuery("SELECT DISTINCT c FROM Supplier c "
            +"LEFT JOIN FETCH c.salesList WHERE c.name LIKE :p1"
            , Supplier.class)
            .setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
            .setParameter("p1", name + "%")
            .list();

【讨论】:

以上是关于一对多关系在不使用“distinct”的情况下获取重复的对象。为啥?的主要内容,如果未能解决你的问题,请参考以下文章

在不使用 distinct 的情况下从查询中获取唯一记录集

Eloquent:如何在不删除任何内容的情况下同步一对多关系(FK 可以为空)?

Entity Framework Core 如何在不使用 Include().ThenInclude() 的情况下从模型中列出一对多从多对多

如何在不使用distinct的情况下显示唯一记录?

在不使用子查询的情况下使用 SELECT DISTINCT ON 计算总行数

CoreData 在一对多关系中编辑一个属性