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

时间:2013-09-11 23:59:36

标签: java hibernate hql distinct one-to-many

我有一对多关系的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。

3 个答案:

答案 0 :(得分:83)

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

  

首先,您需要了解SQL以及OUTER JOIN在SQL中的工作方式。如果   你不完全理解和理解SQL中的外连接,不要   继续阅读此FAQ项目,但请参阅SQL手册或教程。   否则你不会理解以下解释和你   会在Hibernate论坛上抱怨这种行为。典型   可能返回同一Order的重复引用的示例   对象:

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 distinct将直接转换为SQL   不同。不是在这种情况下:你不能过滤掉重复的   SQL级别,产品/连接的本质禁止这个 - 你想要的   重复或您没有获得所需的所有数据。所有这些   当结果集为时,重复的过滤发生在内存中   编组成物体。结果集也应该是显而易见的   基于行的&#34;限制&#34;操作,例如setFirstResult(5)和   setMaxResults(10)不适用于这些急切的获取查询。   如果将结果集限制为特定行数,则会切断   数据随机。有一天,Hibernate可能足够聪明,知道如果   你调用setFirstResult()或setMaxResults()它不应该使用连接,   但是第二个SQL SELECT。试试吧,你的Hibernate版本可能会   已经足够聪明了。如果没有,写两个查询,一个用于限制   东西,另一个渴望获取。你想知道为什么吗?   Criteria查询的示例没有忽略fetch =&#34; join&#34;   设置映射,但HQL不关心?阅读下一个FAQ项目。

答案 1 :(得分:0)

使用结果转换器ol.has.DEVICE_PIXEL_RATIO

Criteria.DISTINCT_ROOT_ENTITY

答案 2 :(得分:0)

这是我从弗拉德·米哈西亚(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();