【问题标题】:When to use the select clause in the JPA criteria API?何时在 JPA 标准 API 中使用 select 子句?
【发布时间】:2015-07-29 05:26:50
【问题描述】:
  1. 不使用CriteriaQuery#select()

    public List<Address> getAddressOfManager(String designation, String name, String orderByColumn) {
    
        Boolean ascending = false;
        CriteriaBuilder cb = emanager.getCriteriaBuilder();
        CriteriaQuery<Address> cq = cb.createQuery(Address.class);
        Root<Address> root = cq.from(Address.class);
        //cq.select(root);  <-------------
        Join<Address, Employee> employeeAddress = root.join(Address_.employee);
        Join<Employee,Project> employeeProject = employeeAddress.join(Employee_.project);
        cq.where(cb.or(cb.equal(employeeProject.get(Project_.name), name),cb.equal(employeeAddress.get(Employee_.designation), designation)));
        Order order = ascending ? cb.asc(root.get(orderByColumn))
                : cb.desc(root.get(orderByColumn));
        cq.orderBy(order);
        List<Address> result = emanager.createQuery(cq).getResultList();
        return result;
    }
    
  2. CriteriaQuery#select()

    public List<Address> getAddressOfManager(String designation, String name, String orderByColumn) {
    
        Boolean ascending = false;
        CriteriaBuilder cb = emanager.getCriteriaBuilder();
        CriteriaQuery<Address> cq = cb.createQuery(Address.class);
        Root<Address> root = cq.from(Address.class);
        cq.select(root); //<----------------
        Join<Address, Employee> employeeAddress = root.join(Address_.employee);
        Join<Employee,Project> employeeProject = employeeAddress.join(Employee_.project);
        cq.where(cb.or(cb.equal(employeeProject.get(Project_.name), name),cb.equal(employeeAddress.get(Employee_.designation), designation)));
        Order order = ascending ? cb.asc(root.get(orderByColumn))
                : cb.desc(root.get(orderByColumn));
        cq.orderBy(order);
        List<Address> result = emanager.createQuery(cq).getResultList();
        return result;
    }
    

    现在,我对何时在 JPA 条件查询中使用 select() 感到困惑。

【问题讨论】:

  • cq.select(root); 是在 JPA 标准中推断出来的,它将选择关联实体中的所有字段,因此从相应的数据库表中选择。但是,当您想选择其中一些时,您需要明确。

标签: hibernate jpa criteria criteria-api


【解决方案1】:

在要指定查询结果的CriteriaQuery&lt;T&gt;接口上指定投影项(或简单地说选择项)基本上有两种方法:

CriteriaQuery<T> select(Selection<? extends T> selection);
CriteriaQuery<T> multiselect(Selection<?>... selections);

通常使用的投影术语是查询本身的候选类(在您的示例中为Address)。它可能像您的第一个示例一样是隐含的。

第一个示例中来自Address 实体的查询没有明确指定其投影项,在这种情况下与明确选择候选类本身相同。

隐式推断出候选持久实体本身作为唯一的投影项。


但是,当查询的投影结果不是候选持久实体本身时,可以使用其他几种构造来塑造查询结果。这些构造在CriteriaBuilder 接口中可用。

塑造查询结果的方法:

CompoundSelection<Y> construct(Class<Y> result, Selection<?>... terms);
CompoundSelection<Object[]> array(Selection<?>... terms);
CompoundSelection<Tuple> tuple(Selection<?>... terms);

为了完整起见,我将尝试通过一个简单的示例依次演示它们中的每一个。


construct() 将查询结果转化为类(非持久实体)的实例:

construct() 方法创建给定类参数的实例,并使用来自输入选择项的值调用(非持久实体的)构造函数。例如,非持久实体(甚至不是实体,纯 Java 类)的构造函数的这些参数必须在数量、顺序和类型(数据类型)上与对应于输入选择项的值匹配。

CriteriaQuery<EmployeeDetails> q = cb.createQuery(EmployeeDetails.class);
Root<Employee> root = q.from(Employee.class);
q.select(cb.construct(EmployeeDetails.class, root.get(Employee_.empName), root.get(Employee_.salary));

EmployeeDetails 在这种情况下,是一个普通的 Java 类——一个非持久实体,它有一个构造函数,它接受两个类型为 String(对于 empName)和 BigDecimal(对于 salary)的参数)。

查询返回List&lt;EmployeeDetails&gt;——来自选定Employees的非持久实体——持久实体。

根据返回的行数,查询还可能使用getSingleResult() 方法返回EmployeeDetails

多个选择项也可以组合成一个复合选择项,代表Object[]Tuple,如下所示。

将查询结果整形为Object[]

CriteriaQuery<Object[]> q = cb.createQuery(Object[].class);
Root<Employee> root = q.from(Employee.class);
q.select(cb.array(root.get(Employee_.empName), root.get(Employee_.salary));
List<Object[]> list = entityManager.createQuery(q).getResultList();

可以看出,查询返回List&lt;Object[]&gt;。此列表的每个元素都包含一个从 0 开始的数组 Objects - Object[]

将查询结果整形为Tuple

CriteriaQuery<Tuple> q = cb.createTupleQuery();
Root<Employee> root = q.from(Employee.class);
Selection<String> empName = root.get(Employee_.empName).alias("empName");
q.select(cb.tuple(empName, root.get(Employee_.salary).alias("salary");
List<Tuple> list = entityManager.createQuery(q).getResultList();

String employeeName = list.get(0).get("empName");//Referring to by using its alias (empName)
String employeeSalary = list.get(0).get(1);//Referring to by using its index (salary)

//Iterate over a list of tuples through a foreach loop using alias.

for (Tuple tuple : list) {
    System.out.println(tuple.get("empName") + " : " + tuple.get("salary"));
}

查询返回List&lt;Tuple&gt;Tuple 保存的值可通过基于 0 的整数索引、使用别名 TupleElement 或直接通过 TupleElement 访问。

CriteriaBuilder#createTupleQuery() 类似于CriteriaBuilder#createQuery(Tuple.class)

使用multiselect() 根据结果类型解释术语:

multiselect() 方法会根据CriteriaQuery 的结果类型自动解释输入术语,以自动得出结果的形状——相关CriteriaQuery 的返回类型。例如,可以使用multiselect() 重写此答案中的第一个示例,如下所示。

CriteriaQuery<EmployeeDetails> q = cb.createQuery(EmployeeDetails.class);
Root<Employee> root = q.from(Employee.class);
q.multiselect(root.get(Employee_.empName), root.get(Employee_.salary));

由于结果的类型是EmployeeDetailsmultiselect() 方法将其参数投影项解释为EmployeeDetails 的构造函数参数。

请注意,在第一个示例中使用的 cb.construct(EmployeeDetails.class, root.get(Employee_.empName), root.get(Employee_.salary) 这样的构造不必在前面使用 multiselect() 的示例中使用,它会根据查询的返回类型自动解释其参数投影术语,并且在结果类中调用适当的构造函数 - EmployeeDetails

如果要指定查询返回Tuple(或Tuples 的列表),则具有完全相同参数的multiselect() 方法将创建Tuple 实例,如下所示。

CriteriaQuery<Tuple> q = cb.createTupleQuery();
Root<Employee> root = q.from(Employee.class);
q.multiselect(root.get(Employee_.empName), root.get(Employee_.salary));

不用说,同样的事情也可以重写以返回Object[](或Object[]s 的列表)。

CriteriaQuery<Object[]> q = cb.createQuery(Object[].class);
Root<Employee> root = q.from(Employee.class);
q.multiselect(root.get(Employee_.empName), root.get(Employee_.salary));

在这两种情况下,muliselect() 方法会根据查询的返回类型(分别为 TupleObject[])自动解释其参数投影项。

还应该注意的是,EcliseLink 有一个bug,在使用multiselect() 以这种方式获取单个Boolean 选择项时仍有待修复,如this question 所示 .


根据不同的 JPA 提供者,multiselect() 方法的行为会因为Object 作为结果类型而变得更加有趣,

  • 如果 multiselect() 方法与单个选择项一起使用,则返回类型是所选项本身。
  • 但是,如果 multiselect() 方法包含多个输入项/选择项/投影项,则结果类型为 Object[]

我不太确定不同的提供者,但multiselect() 方法指定的选择项可能不是数组或集合(或元组值复合项)。唯一可以允许作为multiselect() 参数的复合术语是那些由construct() 方法创建的术语,它基本上依次表示单个元素。


除了上述结构之外,CriteriaQuery#select(Selection&lt;? extends T&gt; selection) 的使用在使用标量/组/聚合/单值函数(例如count()max()min() 等)和子查询时也很常见。为简洁起见,此答案不包括子查询的使用。

Selection 定义查询选择的内容。一个选择可以是 任何对象表达式、属性表达式、函数、子选择、 构造函数或聚合函数。可以为一个别名定义 Selection 使用 alias() API。

CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();

// Count the total employees
CriteriaQuery criteriaQuery = criteriaBuilder.createQuery();
Root employee = criteriaQuery.from(Employee.class);
criteriaQuery.select(criteriaBuilder.count(employee)); //<------
Query query = entityManager.createQuery(criteriaQuery);
Long result = query.getSingleResult();

// Maximum salary
CriteriaQuery criteriaQuery = criteriaBuilder.createQuery();
Root employee = criteriaQuery.from(Employee.class);
criteriaQuery.select(criteriaBuilder.max(employee.get("salary")); //<------
Query query = entityManager.createQuery(criteriaQuery);
Long result = query.getSingleResult();

http://en.wikibooks.org/wiki/Java_Persistence/Criteria#Selection

【讨论】:

  • 谢谢Tiny,但我还是不太清楚。任何供参考的 URL 都会有很大帮助。
  • 您是否在理解代码 sn-ps 或冗长的文本信息时遇到一些困难?这是一个重复多次的单一、简单且精简的示例。我根据我记得的博客回答。我打算引用该博客中的许多内容,但不幸的是,我现在找不到该链接。顺便说一句,具体问题由一条语句回答,“隐含推断出候选持久实体本身作为唯一的投影项。
  • 您好 Tiny,非常感谢。您的回答非常具有描述性,对我理解 JPA 标准很有帮助。尤其是关于预测。
  • 我知道这已经晚了,但是你怎么能在 criteriaQuery.multiselect 中传递 root.get("") 的列表?
猜你喜欢
  • 2016-02-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-11
  • 1970-01-01
  • 2016-01-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多