在 JPA 中, 标准查询是以元模型的概念为基础的. 元模型是为具体持久化单元的受管实体定义的. 这些实体可以是实体类, 嵌入类或者映射的父类. 提供受管实体元信息的类就是元模型类.

描述受管类的状态和他们之间的关系的静态元模型类可以

    1. 从注解处理器产生
    1. 从程序产生
    1. 用 EntityManager 访问.

如下 code, 一个简单的实体类 package com.demo.entities; 下, 实体类 Employee ,假设该实体有诸如 id,name 和 age 的基本属性,还有与类 Address 的 OneToMany 关联:

  1. @Entity
  2. @Table
  3. public class Employee{
  4. private int id;
  5. private String name;
  6. private int age;
  7. @OneToMany
  8. private List<Address> addresses;
  9. // Other code…
  10. }

Employee 类 (com.demo.entities 包中定义) 的标准元模型类的名字将是使用 javax.persistence.StaticMetamodel 注解的 Employee_。元模型类的属性全部是 static 和 public 的。Employee 的每一个属性都会使用在 JPA2 规范中描述的以下规则在相应的元模型类中映射:

  • 诸如 id,name 和 age 的非集合类型,会定义静态属性 SingularAttribute b,这里 b 是定义在类 A 中的类型为 B 的一个对象。
  • 对于 Addess 这样的集合类型,会定义静态属性 ListAttribute b,这里 List 对象 b 是定义在类 A 中类型 B 的对象。其它集合类型可以是 SetAttribute, MapAttribute 或 CollectionAttribute 类型。

以下是用注解处理器产生的元模型类 package com.demo.entities; 下:

  1. import javax.annotation.Generated;
  2. import javax.persistence.metamodel.SingularAttribute;
  3. import javax.persistence.metamodel.ListAttribute;
  4. import javax.persistence.metamodel.StaticMetamodel;
  5. @Generated("org.hibernate.jpamodelgen.JPAMetaModelEntityProcesso")
  6. @StaticMetamodel(Employee.class)
  7. public class Employee {
  8. public static volatile SingularAttribute<Employee, Integer> id;
  9. public static volatile SingularAttribute<Employee, Integer> age;
  10. public static volatile SingularAttribute<Employee, String> name;
  11. public static volatile ListAttribute<Employee, Address> addresses;
  12. }

就像它的名字表明的,注解处理器处理注解,帮助产生源代码。注解处理在编译时就能激活。元模型类遵循 JPA2.0 规范中为定义标准元模型类而描述的规则创建。

使用元模型类最大的优势是凭借其实例化可以在编译时访问实体的持久属性. 该特性使得 criteria 查询更加类型安全.

元模型 API 与 Java 中的标准反射 API 密切相关。主要不同在于使用标准反射 API 编译器无法验证其正确性。例如:下面的代码会通过编译测试:

  1. Class myClass = Class.forName("com.demo.Test");
  2. Field myField = myClass.getField("myName");

编译器假定 com.demo.Test 中定义了属性 myName,一旦该类并没有定义属性 myName,编译器将抛出运行时异常。

元模型 API 会强制编译器检查适当的值是否分配给实体类的持久属性。例如:考虑 Employee 类的 age 属性,它是 Integer 变量。若该属性被赋值为 String 类型的值,编译器会抛出错误。该实现并不要求支持非标准特性。程序员编写的元模型类通常称为非标准元模型类。当 EntityManagerFactory 创建时,持久化提供者会初始化元模型类的属性。

为了更好的理解 criteria 查询,考虑拥有 Employee 实例集合的 Dept 实体,Employee 和 Dept 的元模型类的代码如下:

  1. //All Necessary Imports
  2. @StaticMetamodel(Dept.class)
  3. public class Dept_ {
  4. public static volatile SingularAttribute<Dept, Integer> id;
  5. public static volatile ListAttribute<Dept, Employee> employeeCollection;
  6. public static volatile SingularAttribute<Dept, String> name;
  7. }
  8. //All Necessary Imports
  9. @StaticMetamodel(Employee.class)
  10. public class Employee_ {
  11. public static volatile SingularAttribute<Employee, Integer> id;
  12. public static volatile SingularAttribute<Employee, Integer> age;
  13. public static volatile SingularAttribute<Employee, String> name;
  14. public static volatile SingularAttribute<Employee, Dept> deptId;
  15. }

下面的代码片段展示了一个 criteria 查询,它用于获取所有年龄大于 24 岁的员工:

  1. CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
  2. CriteriaQuery<Employee> criteriaQuery = criteriaBuilder.createQuery(Employee.class);
  3. Root<Employee> employee = criteriaQuery.from(Employee.class);
  4. Predicate condition = criteriaBuilder.gt(employee.get(Employee_.age), 24);
  5. criteriaQuery.where(condition);
  6. TypedQuery<Employee> typedQuery = em.createQuery(criteriaQuery);
  7. List<Employee> result = typedQuery.getResultList();

对应的 SQL: SELECT * FROM employee WHERE age > 24

1.CriteriaBuilder 安全查询创建工厂

CriteriaBuilder 是一个工厂对象, 安全查询的开始. 用于构建 JPA 安全查询. 可以从 EntityManager 或 EntityManagerFactory 类中获得 CriteriaBuilder.
比如: CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();

2.CriteriaQuery 安全查询主语句


CriteriaQuery对象必须在实体类型或嵌入式类型上的 Criteria 查询上起作用。
它通过调用
CriteriaBuilder**, createQuery 或 CriteriaBuilder.createTupleQuery 获得。
CriteriaBuilder 就像 CriteriaQuery 的工厂一样。
CriteriaBuilder 工厂类是调用 EntityManager.getCriteriaBuilder 或 EntityManagerFactory.getCriteriaBuilder 而得。
Employee 实体的 CriteriaQuery 对象以下面的方式创建:

  1. CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
  2. CriteriaQuery<Employee> criteriaQuery = criteriaBuilder.createQuery(Employee.class);

3.Root 定义查询的 From 子句中能出现的类型

AbstractQuery 是 CriteriaQuery 接口的父类。它提供得到查询根的方法。
Criteria 查询的查询根定义了实体类型,能为将来导航获得想要的结果,它与 SQL 查询中的 FROM 子句类似。
Root 实例也是类型化的,且定义了查询的 FROM 子句中能够出现的类型。
查询根实例能通过传入一个实体类型给 AbstractQuery.from 方法获得。
Criteria 查询,可以有多个查询根。
Employee 实体的查询根对象可以用以下的语法获得 :

  1. Root<Employee> employee = criteriaQuery.from(Employee.class);

4.Predicate 过滤条件

过滤条件应用到 SQL 语句的 FROM 子句中。
在 criteria 查询中,查询条件通过 Predicate 或 Expression 实例应用到 CriteriaQuery 对象上。
这些条件使用 CriteriaQuery .where 方法应用到 CriteriaQuery 对象上。
CriteriaBuilder 也是作为 Predicate 实例的工厂,Predicate 对象通过调用 CriteriaBuilder 的条件方法( equal,notEqual, gt, ge,lt, le,between,like 等)创建。
Predicate 实例也可以用 Expression 实例的 isNull, isNotNull 和 in 方法获得,复合的 Predicate 语句可以使用 CriteriaBuilder 的 and, or andnot 方法构建。
下面的代码片段展示了 Predicate 实例检查年龄大于 24 岁的员工实例:

  1. Predicate condition = criteriaBuilder.gt(employee.get(Employee_.age), 24);
  2. criteriaQuery.where(condition);

过 Employee_元模型类 age 属性,称之为路径表达式。若 age 属性与 String 文本比较,编译器会抛出错误,这在 JPQL 中是不可能的。

5.Predicate[] 多个过滤条件

  1. List predicatesList = new ArrayList();
  2. predicatesList.add(.....Pridicate....)
  3. criteriaQuery.where(predicatesList.toArray(new Predicate[predicatesList.size()]));

OR 语句

  1. predicatesList.add(criteriaBuilder.or(criteriaBuilder.equal(root.get(RepairOrder_.localRepairStatus), LocalRepairStatus.repairing),criteriaBuilder.equal(root.get(RepairOrder_.localRepairStatus), LocalRepairStatus.diagnos)));

忽略大小写 (全大写)

  1. predicatesList.add(criteriaBuilder.like(criteriaBuilder.upper(root.get(RepairShop_.shopName)), StringUtils.upperCase(StringUtils.trim(this.shopName)) + "%"));

通过如上两句添加多个.

6.TypedQuery执行查询与获取元模型实例

注意,你使用 EntityManager 创建查询时,可以在输入中指定一个 CriteriaQuery 对象,它返回一个 TypedQuery,它是 JPA 2.0 引入 javax.persistence.Query 接口的一个扩展,TypedQuery 接口知道它返回的类型。

所以使用中, 先创建查询得到 TypedQuery, 然后通过 typeQuery 得到结果.

当 EntityManager.createQuery(CriteriaQuery) 方法调用时,一个可执行的查询实例会创建,该方法返回指定从 criteria 查询返回的实际类型的 TypedQuery 对象。

TypedQuery 接口是 javax.persistence.Queryinterface. 的子类型。在该片段中, TypedQuery 中指定的类型信息是 Employee,调用 getResultList 时,查询就会得到执行

  1. TypedQuery typedQuery = em.createQuery(criteriaQuery);
  2. List result = typedQuery.getResultList();

元模型实例通过调用 EntityManager.getMetamodel 方法获得,EntityType的元模型实例通过调用 Metamodel.entity(Employee.class) 而获得,其被传入 CriteriaQuery.from 获得查询根。

  1. Metamodel metamodel = em.getMetamodel();EntityType<Employee>
  2. Employee_ = metamodel.entity(Employee.class);
  3. Root<Employee> empRoot = criteriaQuery.from(Employee_);

也有可能调用 Root.getModel 方法获得元模型信息。类型 EntityType的实例 Dept_和 name 属性可以调用 getSingularAttribute 方法获得,它与 String 文本进行比较:

  1. CriteriaQuery criteriaQuery = criteriaBuilder.createQuery();
  2. Root<Dept> dept = criteriaQuery.from(Dept.class);
  3. EntityType<Dept> Dept_ = dept.getModel();
  4. Predicate testCondition = criteriaBuilder.equal(dept.get(Dept_.getSingularAttribute("name", String.class)), "Ecomm");

7.Expression 用在查询语句的 select,where 和 having 子句中,该接口有 isNull, isNotNull 和 in 方法

Expression 对象用在查询语句的 select,where 和 having 子句中,该接口有 isNull, isNotNull 和 in 方法,下面的代码片段展示了 Expression.in 的用法,employye 的年龄检查在 20 或 24 的。

  1. CriteriaQuery<Employee> criteriaQuery = criteriaBuilder .createQuery(Employee.class);
  2. Root<Employee> employee = criteriaQuery.from(Employee.class);
  3. criteriaQuery.where(employee.get(Employee_.age).in(20, 24));
  4. em.createQuery(criteriaQuery).getResultList();

对应的 SQL: SELECT * FROM employee WHERE age in (20, 24)

下面也是一个更贴切的例子:

  1. //定义一个Expression
  2. Expression<String> exp = root.get(Employee.id);
  3. //
  4. List<String> strList=new ArrayList<>();
  5. strList.add("20");
  6. strList.add("24");
  7. predicatesList.add(exp.in(strList));
  8. criteriaQuery.where(predicatesList.toArray(new Predicate[predicatesList.size()]));

8. 复合谓词

Criteria Query 也允许开发者编写复合谓词,通过该查询可以为多条件测试下面的查询检查两个条件。首先,name 属性是否以 M 开头,其次,employee 的 age 属性是否是 25。逻辑操作符 and 执行获得结果记录。

  1. criteriaQuery.where(
  2. criteriaBuilder.and(
  3. criteriaBuilder.like(employee.get(Employee_.name), "M%"),
  4. criteriaBuilder.equal(employee.get(Employee_.age), 25)
  5. ));
  6. em.createQuery(criteriaQuery).getResultList();

连接查询

在 SQL 中,连接跨多张表以获取查询结果,类似的实体连接通过调用 From.join 执行,连接帮助从一个实体导航到另一个实体以获得查询结果。
Root 的 join 方法返回一个 Join 类型 (也可以是 SetJoin,,ListJoin,MapJoin 或者 CollectionJoin 类型)。

默认情况下,连接操作使用内连接,而外连接可以通过在 join 方法中指定 JoinType 参数为 LEFT 或 RIGHT 来实现。

  1. CriteriaQuery<Dept> cqDept = criteriaBuilder.createQuery(Dept.class);
  2. Root<Dept> deptRoot = cqDept.from(Dept.class);
  3. Join<Dept, Employee> employeeJoin = deptRoot.join(Dept_.employeeCollection);
  4. cqDept.where(criteriaBuilder.equal(employeeJoin.get(Employee_.deptId).get(Dept_.id), 1));
  5. TypedQuery<Dept> resultDept = em.createQuery(cqDept);

抓取连接

当涉及到 collection 属性时,抓取连接对优化数据访问是非常有帮助的。这是通过预抓取关联对象和减少懒加载开销而达到的。
使用 criteria 查询,fetch 方法用于指定关联属性
Fetch 连接的语义与 Join 是一样的,因为 Fetch 操作不返回 Path 对象,所以它不能将来在查询中引用。
在以下例子中,查询 Dept 对象时 employeeCollection 对象被加载,这不会有第二次查询数据库,因为有懒加载。

  1. CriteriaQuery<Dept> d = cb.createQuery(Dept.class);
  2. Root<Dept> deptRoot = d.from(Dept.class);
  3. deptRoot.fetch("employeeCollection", JoinType.LEFT);
  4. d.select(deptRoot);
  5. List<Dept> dList = em.createQuery(d).getResultList();

对应 SQL: SELECT * FROM dept d, employee e WHERE d.id = e.deptId

路径表达式

Root 实例,Join 实例或者从另一个 Path 对象的 get 方法获得的对象使用 get 方法可以得到 Path 对象,当查询需要导航到实体的属性时,路径表达式是必要的。
Get 方法接收的参数是在实体元模型类中指定的属性。
Path 对象一般用于 Criteria 查询对象的 select 或 where 方法。例子如下:

  1. CriteriaQuery<String> criteriaQuery = criteriaBuilder.createQuery(String.class);
  2. Root<Dept> root = criteriaQuery.from(Dept.class);
  3. criteriaQuery.select(root.get(Dept_.name));&nbsp;

参数化表达式

在 JPQL 中,查询参数是在运行时通过使用命名参数语法 (冒号加变量,如 :age) 传入的。在 Criteria 查询中,查询参数是在运行时创建 ParameterExpression 对象并为在查询前调用 TypeQuery,setParameter 方法设置而传入的。下面代码片段展示了类型为 Integer 的 ParameterExpression age,它被设置为 24:

  1. ParameterExpression<Integer> age = criteriaBuilder.parameter(Integer.class);
  2. Predicate condition = criteriaBuilder.gt(testEmp.get(Employee_.age), age);
  3. criteriaQuery.where(condition);
  4. TypedQuery<Employee> testQuery = em.createQuery(criteriaQuery);
  5. List<Employee> result = testQuery.setParameter(age, 24).getResultList();
  6. Corresponding SQL: SELECT * FROM Employee WHERE age = 24;

排序结果

Criteria 查询的结果能调用 CriteriaQuery.orderBy 方法排序,该方法接收一个 Order 对象做为参数。通过调用 CriteriaBuilder.asc 或 CriteriaBuilder.Desc,Order 对象能被创建。以下代码片段中,Employee 实例是基于 age 的升序排列。

  1. CriteriaQuery<Employee> criteriaQuery = criteriaBuilder .createQuery(Employee.class);
  2. Root<Employee> employee = criteriaQuery.from(Employee.class);
  3. criteriaQuery.orderBy(criteriaBuilder.asc(employee.get(Employee_.age)));
  4. em.createQuery(criteriaQuery).getResultList();

对应 SQL: SELECT * FROM Employee ORDER BY age ASC

分组

CriteriaQuery 实例的 groupBy 方法用于基于 Expression 的结果分组。查询通过设置额外表达式,以后调用 having 方法。下面代码片段中,查询按照 Employee 类的 name 属性分组,且结果以字母 N 开头:
CriteriaQuery cq = criteriaBuilder.createQuery(Tuple.class);

  1. Root<Employee> employee = cq.from(Employee.class);
  2. cq.groupBy(employee.get(Employee_.name));
  3. cq.having(criteriaBuilder.like(employee.get(Employee_.name), "N%"));
  4. cq.select(criteriaBuilder.tuple(employee.get(Employee_.name),criteriaBuilder.count(employee)));
  5. TypedQuery<Tuple> q = em.createQuery(cq);
  6. List<Tuple> result = q.getResultList();

对应 SQL: SELECT name, COUNT(*) FROM employeeGROUP BY name HAVING name like ‘N%’

查询投影

Criteria 查询的结果与在 Critiria 查询创建中指定的一样。结果也能通过把查询根传入 CriteriaQuery.select 中显式指定。Criteria 查询也给开发者投影各种结果的能力。

使用 construct()

使用该方法,查询结果能由非实体类型组成。在下面的代码片段中,为 EmployeeDetail 类创建了一个 Criteria 查询对象,而 EmployeeDetail 类并不是实体类型。

  1. CriteriaQuery<EmployeeDetails> criteriaQuery = criteriaBuilder.createQuery(EmployeeDetails.class);
  2. Root<Employee> employee = criteriaQuery.from(Employee.class);
  3. criteriaQuery.select(criteriaBuilder.construct(EmployeeDetails.class, employee.get(Employee_.name), employee.get(Employee_.age)));
  4. em.createQuery(criteriaQuery).getResultList();
  5. Corresponding SQL: SELECT name, age FROM employee<span style="white-space: normal;">&nbsp;</span>

返回 Object[]的查询

Criteria 查询也能通过设置值给 CriteriaBuilder.array 方法返回 Object[]的结果。下面的代码片段中,数组大小是 2(由 String 和 Integer 组成)。

  1. CriteriaQuery<Object[]> criteriaQuery = criteriaBuilder.createQuery(Object[].class);
  2. Root<Employee> employee = criteriaQuery.from(Employee.class);
  3. criteriaQuery.select(criteriaBuilder.array(employee.get(Employee_.name), employee.get(Employee_.age)));
  4. em.createQuery(criteriaQuery).getResultList();

对应 SQL: SELECT name, age FROM employee

返回元组 (Tuple) 的查询

数据库中的一行数据或单个记录通常称为元组。通过调用 CriteriaBuilder.createTupleQuery() 方法,查询可以用于元组上。CriteriaQuery.multiselect 方法传入参数,它必须在查询中返回。

  1. CriteriaQuery<Tuple> criteriaQuery = criteriaBuilder.createTupleQuery();
  2. Root<Employee> employee = criteriaQuery.from(Employee.class);
  3. criteriaQuery.multiselect(employee.get(Employee_.name).alias("name"), employee.get(Employee_.age).alias("age"));
  4. em.createQuery(criteriaQuery).getResultList();

对应 SQL: SELECT name, age FROM employee

Criteria 查询是一种以更加面向对象的方式查询数据库的方法、在本文中,我讨论了 JPA2 中类型安全的 Criteria 查询,以及对于理解 Criteria 查询非常重要的元模型的概念。也讨论了 Criteria 查询中的各种 API。

来源 | https://www.developer.com/java/ent/article.php/3902911/Querying-in-JPA-2-Typesafe-and-Object-Oriented.htm