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

her*_*rti 50 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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

因此,我必须使用不同的:

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

这种行为是预期的吗?我认为这与SQL有所不同.

ger*_*tan 90

这个问题在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();  
Run Code Online (Sandbox Code Playgroud)
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)

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();       
Run Code Online (Sandbox Code Playgroud)

最后一个是特别的.看起来您在这里使用SQL DISTINCT关键字.当然,这不是SQL,这是HQL.在这种情况下,这个不同只是结果转换器的快捷方式.是的,在其他情况下,HQL distinct将直接转换为SQL DISTINCT.不是在这种情况下:您无法在SQL级别过滤掉重复项,产品/连接的本质禁止这样做 - 您需要重复项或者您没有获得所需的所有数据.当结果集被编组到对象中时,所有这些重复的过滤都发生在内存中.同样显而易见的是,为什么基于行的"限制"操作(例如setFirstResult(5)和setMaxResults(10))不适用于这些类型的eager fetch查询.如果将结果集限制为特定行数,你随机切断数据.有一天,Hibernate可能足够聪明,知道如果你调用setFirstResult()或setMaxResults(),它不应该使用连接,而应该使用第二个SQL SELECT.试试吧,你的Hibernate版本可能已经足够智能了.如果没有,写两个查询,一个用于限制东西,另一个用于急切提取.你想知道为什么带有Criteria查询的例子没有忽略映射中的fetch ="join"设置,但是HQL并不关心?阅读下一个FAQ项目.你想知道为什么带有Criteria查询的例子没有忽略映射中的fetch ="join"设置,但是HQL并不关心?阅读下一个FAQ项目.你想知道为什么带有Criteria查询的例子没有忽略映射中的fetch ="join"设置,但是HQL并不关心?阅读下一个FAQ项目.