一对多关系在不使用“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 > 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”的情况下获取重复的对象。为啥?的主要内容,如果未能解决你的问题,请参考以下文章
Eloquent:如何在不删除任何内容的情况下同步一对多关系(FK 可以为空)?
Entity Framework Core 如何在不使用 Include().ThenInclude() 的情况下从模型中列出一对多从多对多