我应该在此JPQL查询中包括非重复字词吗? 背景我所缺少的结论附录

问题描述

背景

我在SO上和许多流行的博客中都看到了关于JPQL distinct查询JOIN FETCH关键字的必要性和PASS_disTINCT_THROUGH查询提示的多个答案和问题。

例如,看到这两个问题

以及这些博客文章

我所缺少的

现在,我的问题是我无法完全理解JPQL查询中何时必须确切地包含distinct关键字。更具体地说,是否取决于执行查询方法getResultListgetSingleResult)。

下面是一个例子,以阐明我的意思。

从现在开始,我写的所有东西都在Ubuntu Linux 18.04,Java 8,Hibernate 5.4.13和内存H2数据库(1.4.200版)中进行了测试。

假设我有一个Department实体,该实体与DepartmentDirector实体具有 lazy 双向一对多关系:

// Department.java
@Entity
public class Department {
    // ...
    private Set<DepartmentDirector> directors;

    @OnetoMany(mappedBy = "department",fetch = FetchType.LAZY)
    public Set<DepartmentDirector> getDirectors() {
        return directors;
    }
    // ...
}

// DepartmentDirector.java
@Entity
public class DepartmentDirector {
    // ...
    private Department department

    @ManyToOne
    @JoinColumn(name = "department_fk")
    public Department getDepartment() {
        return department;
    }
    // ...
}

假设我的数据库当前包含一个部门(department1)和与其相关联的两个主管。

现在,我想通过其uuid(主键)及其所有主管来检索部门。这可以通过以下JOIN FETCH JPQL查询来完成:

String query = "select department from Department department left join fetch "
             + "department.directors where department.uuid = :uuid";

由于前面的查询使用带有子集合的join fetch,所以我希望它在发出时返回两个重复的部门:但是,仅在将查询getResultList方法一起使用时才会发生,而在使用getSingleResult方法。这在某种程度上是合理的,但是我发现getSingleResult的Hibernate实现在幕后使用getResultList,因此我希望抛出NonUniqueResultException

我还简要地介绍了JPA 2.2规范,但是没有提及在处理两种方法间的重复项方面的区别,并且与此问题相关的每个代码示例都使用getResultList方法

结论

在我的示例中,我发现用JOIN FETCH执行的getSingleResult查询不会遇到重复的实体问题,这在我在背景部分中链接的资源中得到了解释。

如果以上声明正确无误,则意味着如果与JOIN FETCH执行相同的distinct查询,则需要getResultList,而与{{1}执行时,则不需要}}。

如果这是预期的或者我误解了一些东西,我需要有人向我解释。


附录

两个查询的结果:

  1. 查询使用getSingleResult方法运行。我得到了预期的两个重复部门(这仅仅是为了测试查询的行为,为此应使用getResultList

    getSingleResult
  2. 查询使用List<Department> resultList = entityManager.createquery(query,Department.class) .setParameter("uuid",department1.getUuid()) .getResultList(); assertthat(resultList).containsExactly(department1,department1); // passes 方法运行。我希望可以检索相同的重复部门,从而抛出getSingleResult。取而代之的是,只检索一个部门,一切都很好:

    NonUniqueResultException

解决方法

有趣的问题。

首先让我指出,getSingleResult()用于由于其性质总是返回单个结果的查询(意味着:主要是汇总的查询,例如SELECT SUM(e.id) FROM Entity e) 。根据某些特定于业务领域的规则,您认为的查询应返回单个结果,实际上并没有资格。

话虽如此,JPA规范指出,当查询返回多个结果时,getSingleResult()应该抛出NonUniqueResultException

当调用NonUniqueResultExceptionQuery.getSingleResult时,持久性提供程序将抛出TypedQuery.getSingleResult,并且该查询有多个结果。如果当前事务处于活动状态,则此异常不会导致将当前事务标记为回滚。

但是,请查看Hibernate实现:

    @Override
    public R getSingleResult() {
        try {
            final List<R> list = list();
            if ( list.size() == 0 ) {
                throw new NoResultException( "No entity found for query" );
            }
            return uniqueElement( list );
        }
        catch ( HibernateException e ) {
            if ( getProducer().getFactory().getSessionFactoryOptions().isJpaBootstrap() ) {
                throw getExceptionConverter().convert( e );
            }
            else {
                throw e;
            }
        }
    }

    public static <R> R uniqueElement(List<R> list) throws NonUniqueResultException {
        int size = list.size();
        if ( size == 0 ) {
            return null;
        }
        R first = list.get( 0 );
        for ( int i = 1; i < size; i++ ) {
            if ( list.get( i ) != first ) {
                throw new NonUniqueResultException( list.size() );
            }
        }
        return first;
    }

事实证明,Hibernate对“多个结果”的解释似乎是“多个唯一结果”。

事实上,我与所有JPA提供程序一起测试了您的方案,事实证明:

  • Hibernate确实会从getResultList()返回重复项,但由于实现getSingleResult()的特殊方式,不会引发异常
  • EclipseLink是唯一一个不会遭受getResultList()中的重复结果错误困扰的人,因此getSingleResult()也不 抛出异常(对我来说,这行为只是合乎逻辑的,但事实证明,这全都是解释问题)
  • OpenJPA和DataNucleus都从getResultList()返回重复的结果,并从getSingleResult()抛出异常。

Tl; DR

如果这是预期的或者我误解了一些东西,我需要有人向我解释。

这实际上归结为您对规范的解释