Pro JPA2 第九章(条件API)
9.1 概述
通过编程API来构建查询.- 9.1.1 条件API
SELECT e
FROM Employee e
WHERE e.name = 'John Smith'
以下是使用条件API所构建的等价查询:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
Root<Employee<> emp = c.from(Emplpyee.class);
c.select(emp);
.where(cb.equal(emp.get("name"),"Join Smith"));
- 9.1.2 参数化类型
为了能够确保类型安全和使代码更易读,请尽量使用Java泛型,如上例. 9.1.3 动态查询
使用条件API的员工搜索public class SearchService { @PersistenceContext(unitName="EmployeeHR") EntityManager em; public List<Employee> findEmployees(String name,String deptName,String projcetName,String city) { CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Employee> c = cb.createQuery(Employee.class); Rooot<Employee> emp = c.from(Employee.class); c.select(emp); c.distinct(true); Join<Employee,Project> project = emp.join("projects",JoinType.LEFT); List<Predicate> criteria = new ArrayList<Predicate>(); if(name != null) { ParameterExpression<String> p = cb.parameter(String.class,"name"); criteria.add(cb.equal(emp.get("name"),p)); } if(deptName != null) { ParameterExpression<String> p = cb.parameter(String.class."dept"); criteria.add(cb.equal(emp.get("dept").get("name"),p)); } if(projectName!= null) { ParameterExpression<String> p = cb.parameter(String.class."project"); criteria.add(cb.equal(emp.get("address").get("city"),p)); } if(criteria.size() == 0) { throw new RuntimeException("no criteria"); } else if (criteria.size() == 1) { c.where(criteria.get(0)); } else { c.where(cb.and(criteria.toArray(new Predicate[0]))); } TypedQuery<Employee> q = em.createQuery(c); if(name != null) {q.setParameter("name",name);} if(deptName!= null) {q.setParameter("dept",deptName);} if(project!= null) {q.setParameter("project",projectName);} if(city!= null) {q.setParameter("city",city);} return q.getResultList(); } }
- 9.2 构建条件API查询
- 9.2.1 创建查询定义
正如前边所说的,条件API的核心是CriteriaBuilder接口,可通过调用EntityManager接口的getCriteriaBuilder()方法获得.根据查询预期的结果类型,CriteriaBuilder接口提供了三种通过条件API创建新的查询定义的方法.第一种是createQuery(Class)方法,传入对应于查询结果的类.第二种方法是createQuery(),它没有任何参数,对应结果类型为Object的查询.第三种方法是createTupleQuery()方法,它用于投影或者报告查询,其中查询的SELECT子句中包含一个以上的表达式,以及您希望以一种更强类型的方式使用结果,它实际上只是一种便捷方法,相当于调用createQuery(Tuple.class).
条件API是由一些组合在一起建模JPA查询结构的接口组成的.随着您在本章中的学习进程,查看下图或许有所帮助. 9.2.2 基本结构
SELECT,FROM,WHERE,ORDER BY,GROUP BY以及HAVING在条件API的查询定义接口中存在一种等价的方法.JP QL子句 条件API接口 方法 SELECT CriteriaQuery select() null Subquery select() FROM AbstractQuery from() WHERE AbstractQuery where() ORDER BY CriteriaQuery orderBy() GROUP BY AbstractQuery groupBy() HAVING AbstractQuery having() - 9.2.3 条件对象和可变性
条件API的典型用户将会导致创建许多不同的对象.除了主要的CriteriaBuilder和CriteriaQuery对象之外,每个表达式的 每个组件都是由不同的对象进行表示,然而并非所有的对象都是平等的,为了有效地使用条件API,需要熟悉在其设计中所假定的编码模式.
需要考虑的第一个问题是可变性(mutability).通过条件API创建的大多数对象实际上不可变.使用不可变对象意味着传递到CriteriaBuilder方法的参数具有丰富的细节.必须传入一切有关信息,从而可以在其创建之时完成该对象.这种方法的有点是它租金了方法的链式调用.所以用于构建表达式的方法所返回的对象不必调用改变方法,所以控制可以立即继续到达表达式中的下一个组件.
只有创建查询定义对象的CriteriaBuilder方法真正会产生可变的结果.其预计会通过调用如select(),from()和where()等方法,对CriteriaQuery和Subquery对象进行多次修改.但是也要注意,在多数情况下,调用一个方法两次会一所提供的参数替换对象中的内容.
然儿,在一些情况下,调用一个方法两次实际上是产生迭加效果.以不同的参数调用from()两次会导致把多个查询根添加到查询定义.
第二个问题是条件API对象上的getter方法的存在,它们如预期一样,返回关于每个对象所表示的查询的组件信息.但是需要注意的是:这种方法的主要作用是为了以真正通用的方式给开发人员使用查询定义提供便利.在绝大多数情况下,在构建条件API时,您不必使用getter方法.
- 9.2.3 条件对象和可变性
- 9.2.4 查询根和路径表达式
- 查询根
需要重新审视的第一个基本概念是在JP QL查询的FROM子句中使用的标识变量,其作为包括实体,可嵌入对象和其他抽象架构类型的别名.在JP QL中,标识变量发挥了中心作用,因为它是把查询的不同子句绑定在一起的关键.但是,对于条件API,我们以对象表示查询组件,因此很少存在用来关注我们自身的别名.为了定义一个FROM子句,仍然需要一种方法来表示我们感兴趣查询的抽象架构类型.
AbstractQuery接口(CrieriaQuery的父类)提供from()方法来定义抽象架构类型,它将形成查询的基础.该方法接受一个实体类型作为参数,并把一个新的根条件到查询中.条件查询中的根对应JP QL中的一个标识变量,它反过来对应了一个值域变量声明或联接表达式.
CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
Root<Employee> emp = c.from(Employee.class);
如果在WHERE子句中没有进一步地限制,那么当定义了一个以上的根时,将会导致一个笛卡尔乘积.
SELECT DISTINCT d
FROM Department d,Employee e
WHERE d = e.department
为了把此查询转换成条件API,需要调用from()两次,添加Department和Employee实体作为查询根.
CriteriaQuery<Department> c = cb.createQuery<Department.class>;
Root<Department> dept = c.from(Department.class);
Root<Employee> emp = c.from(Employee.class);
c.select(dept).distinct(true).where(cb.equal(dept,emp.get("department)));
- 路径表达式
需要审视的第二个概念是路径表达式.路径表达式是JP QL语言的力量和灵活性的关键,同事也是条件API的一个中心部分.
SELECT e
FROM Employee e
WHERE e.address.city = 'New York'
根据条件API
emp.get("address").get("city");
因为构造路径表达式的结果是一个可以用来构建条件表达式的Expression对象,所以能够表示完成的查询如下:
CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
Root<Employee> emp = c.from(Employee.class);
c.select(emp).where(cb.equal(emp.get("address).get("city),"New York"));
- 查询根
- 9.2.5 SELECT子句
- 选择单个表达式
迄今为止,我们一直在把一个查询根传递到select()方法,从而指示我们希望实体是查询的结果,其实也可以提供一个单值表达式,如选择一个实体的特性或任何兼容的标量表达式.
CriteriaQuery<String> c = cb.createQuery(String.class);
Root<Employee> emp = c.from(Employee.class);
c.select(emp.<String>get("name"));
注意其中使用了不一样的语法来声明”name”是String类型的.提供给select()方法的表达式类型必须与用来创建CriteriaQuery对象的结果类型兼容.当一个对象调用使用泛型类型来强制兼容性约束时,该类型可以作为方法名称的前缀,从而在不能自动确定类型的情况下对其进行限定:
CriteriaQuery<T> select(Selection<? extends T> selection);
select()的参数必须是一种与查询定义的结果类型兼容的类型. - 选择多个表达式
当定义设计一个以上的表达式的SELECT子句时,所需的条件API方法将依赖于如何创建查询定义.如果结果类型是Tuple,那么必须把CompoundSelection< Tuple >对象传递给select().如果结果类型是一个非持久化的类,且将使用构造函数表达式来创建它,那么参数必须是一个CompoundSelection< [T] >对象,其中[T]是非持久化的类类型(class type).最后,如果结果类型是一个对象数组,那么必须提供一个CompoundSelection< Object[] >对象.分别调用CriteriaBuilder接口的tuple(),construct()和array()方法来创建这些对象.
CriteriaQuery<Tuple> c = cb.createTupleQuery();
Root<Employee> emp = c.from(Employee.class);
c.select(cb.tuple(emp.get("id"),emp.get("name)));
为了方便,也可以使用CriteriaQuery接口的multiselect()方法来设置SELECT子句.给定查询的结果类型,multiselect()方法将创建适当的参数类型.取决于如何创建查询的定义,它可以采取三种形式.
第一种形式是针对把Object或者Object[]作为结果类型的查询.
CriteriaQuery<Object[]> c = cb.createQuery(Object[].class);
Root<Employee> emp = c.from(Employee.class);
c.multiselect(emp.get("id"),emp.get("name));
第二种形式
CriteriaQuery<Tuple> c = cb.createTupleQuery();
Root<Employee> emp = c.from(Employee.class);
c.multiselect(emp.get("id"),emp.get("name));
第三种形式是针对具有构造函数表达式的查询,它将产生非持久化的类型.
CriteriaQuery<EmployeeInfo> c = cb.createQuery(EmployeeInfo.class);
Root<Employee> emp = c.from(Employee.class);
c.multiselect(emp.get("id"),emp.get("name));
这种形式等价于:
CriteriaQuery<EmployeeInfo> c = cb.createQuery(EmployeeInfo.class);
Root<Employee> emp = c.from(Employee.class);
c.select(cb.construct(EmployeeInfo.class,emp.get("id"),emp.get("name")));
虽然multiselect()方法对于构造表达式很方便,但是扔存在需要使用CriteriaBuilder接口的construct()方法的情况.例如,如果查询的结果类型是Object[],并且它还包括了一个针对部门结果的构造函数表达式
CriteriaQuery<Object[]> c = cb.createQuery(Object[].class);
Root<Employee> emp = c.from(Employee.class);
c.multiselect(emp.get("id"),cb.contruct(Employee.class,emp.get("id"),emp.get("name")));
- 使用别名
CriteriaQuery<Tuple> c = cb.createTupleQuery();
Root<Employee> emp = c.from(Employee.class);
c.multiselect(emp.get("id").alias("id"),emp.get("name").alias("fullName"));
取值:
TypedQuery<Tuple> q = em.createQuery(c);
for(Tuple t : q.getResultList()) {
String id = t.get("id",String.class);
String fullName = t.get("fullName",String.class);
}
- 选择单个表达式
- 9.2.6 FROM子句
- 内部联接和外部联接
联接表达式通过From接口的join()方法来创建.
当联接一个集合类型(除Map之外)时,联接将由两个参数化类型:源类型和目标类型.这回在联接的双方维持类型安全.并明确将要联接的类型.
join()方法是可以叠加的,所以每个调用都将导致创建一个新的联接.
Join<Employee,Project> project = emp.join("projects",JoinType.LEFT);
如果不写JoinType.LEFT,则默认是内部链接.
Join<Employee,Employee> directs = emp.join("directs");
Join<Employee,Project> projects = directs.join("projects");
Join<Employee,Department> dept = directs.join("dept");
联接也可能在单个语句中级联,结果联接将根据语句中最后联接的源和目标来确定类型:
Join<Employee,Project> project = dept.join("employees").join("projects");
- 获取联接
CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
Root<Employee> emp = c.from(Employee.class);
emp.fetch("address");
c.select(emp);
- 内部联接和外部联接
- 9.2.7 WHERE 子句
在条件API中通过AbstractQuery接口的where()方法设置查询的WHERE子句.where()方法接受零个或多个Predict(谓词)对象,或单个Expression参数.每次调用where()将会呈现丢弃任何先前设置的WHERE表达式,并替换为新传入的表达式. 9.2.8 构建表达式
下表汇总了在CriteriaBuilder接口中和JP QL等效的操作符,表达式和函数JP QL操作符 CriteriaBuilder方法 AND and() OR or() NOT not() = equal() <> notEqual() > greaterThan(),gt() =
greaterThanOrEqualTo(),ge() < lessThan(),lt() <= lessThanOrEqualTo(),le() BETWEEN between() IS NULL isNull() IS NOT NULL isNotNull() EXISTS exists() NOT EXISTS not(exists()) IS EMPTY isEmpty() IS NOT EMPTY isNotEmpty() MEMBER OF isMember() NOT MEMBER OF isMember() LIKE like() NOT LIKE notLike() IN in() NOT IN not(in()) ALL all() ANY any() SOME some() - neg(),diff() + sum() * prod() / quot() COALESCE coalesce() NULLIF nullif() CASE selectCase() ABS abs() CONCAT concat() CURRENT_DATE currentDate() CURRENT_TIME currentTime() CURRENT_TIMESTAMP currentTimestamp() LENGTH length() LOCATE locate() LOWER lower() MOD mod() SIZE size() SQRT sqrt() SUBSTRING substring() UPPER upper() TRIM trim() AVG avg() SUM sum(),sumAsLong(),sumAsDouble() MIN min(),least() MAX max(),greatest() COUNT count() COUNT DISTINCT countDistinct() 谓词
对于那些希望逐步建立表达式而不是把它们作为一个列表的人,条件API还提供了构建AND和OR表达式的一种不同的样式.CriteriaBuilder接口的conjunction()和disjunction()方法可以创建Predicate对象,其总是分别解析到true和false.一旦获取之后,这些原始谓词就可以与其他的谓词相组合,简历树状的嵌套条件表达式.Predicate criteria = cb.conjunction(); if(name != null) { ParameterExpression<String> p = cb.parameter(String.class,"name"); criteria = cb.and(criteria,cb.equal(emp.get("name"),p)); }
字面量
当采用条件API表示时,字面量值可能需要进行特殊处理,在我们已经遇到的素有情况中,需要重载方法以使用Expression对象和Java字面量.然而,可能存在一些只接受一个Expression对象的情况.如果遇到这种情况,那么为了使用这些带Java字面量的表达式,必须使用literal()方法来包装字面量.nullLiteral()方法可以创建NULL字面量,该方法接受一个类参数,并产生一种有类型版本的NULL以匹配传入的类.为了把API的强类型扩展为NULL值,这是必需的.- 参数
条件API查询的参数处理不同于JP QL,虽然JP QL字符串可以简单地以冒号作为字符串名的前缀以表示参数别名,但是这种技术在条件APi中不可用.相反,我们必须显式地创建一个正确类型的可用于条件表达式的ParameterExpression.这可以通过CriteriaBuilder接口的parameter()方法来实现.
CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
Root<Employee> emp = c.from(Employee.class);
c.select(emp);
ParameterExpression<String> deptName = cb.parameter(String.class,"deptName");
c.where(cb.equal(emp.get("dept").get("name"),deptName));
如果不会在查询的其他部分对参数进行重用,那么可以把它直接嵌入到谓词表达式中,以十整个查询定义变得更加简明.
CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
Root<Employee> emp = c.from(Employee.class);
c.select(emp).where(cb.equal(emp.get("dept").get("name"),cb.parameter(String.class,"deptName")));
子查询
AbstractQuery接口提供了subquery()方法来创建子查询,子查询可以是相关的或者不相关的.条件API同时支持相关和不相关的子查询,再一次使用查询根把各个子句和表达式联系在一起.subquery()的参数是一个类的实例,其表示子查询的结果类型.返回值是一个Subquery实例,它本身是AbstractQuery的扩展.除了限制方法是用于构建子句外,Subquery实例完全类似于CriteriaQuery的查询定义,可用于创建简单和复杂的查询.public class SearchService { @PersistenceContext(unitName = "EmployeeHR") EntityManager em; public List<Employee> findEmployees(String name,String deptName,String projectName,String city) { CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Employee> c = cb.createQuery(Employee.class); Root<Employee> emp = c.from(Employee.class); c.select(emp); if(prjectName != null) { Subquery<Employee> sq = c.subquery(Employee.class); Root<Project> project = sq.from(Project.class); Join<Project,Employee> sqEmp = project.join("employees"); sq.select(sqEmp).where(cb.equal(project.get("name"),cb.parameter(String.class,"project"))); criteria.add(cb.in(emp).value(sq)); } } }
为了使用条件API来创建这个查询,需要把查询基于来自父查询的Root对象,但是from()方法只接受持久化的类类型,解决方案是Subquery接口的correlate()方法,它执行与AbstractQuery接口的from()方法类似的功能,但是利用父查询的Root和Join对象来完成工作.
if(projectName != null) { Subquery<Project> sq = c.subquery(Project.class); Root<Employee> sqEmp = sq.correlate(emp); Join<Employee,Project> project = sqEmp.join("projects"); sq.select(project).where(cb.equal(project.get("name"),cb.parameter(String.class,"project"))); criteria.add(cb.exists(sq)); }
- IN
与其他操作符不同,IN操作符在条件API中需要一些特殊的处理,CriteriaBuilder接口的in方法只接受单个参数,单值的表达式将根据IN表达式中的值来进行测试.
CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
Root<Employee> emp = c.from(Employee.class);
c.select(emp).where(cb.in(emp.get("address").get("state")).value("NY").value("CA"));
使用子查询的IN表达式
CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
Root<Employee> emp = c.from(Employee.class);
Subquery<Department> sq = c.subquery(Department.class);
Root<Department> dept = sq.from(Department.class);
Join<Employee,Project> project = dept.join("employees").join("projects");
sq.select(dept).distinct(true).where(cb.like(project.<String>get("name"),"QA%"));
c.select(emp).where(cb.in(emp.get("dept")).value(sq));
- 9.2.9 ORDER BY 子句
CriteriaQuery<Tuple> c = cb.createQuery(Tuple.class);
Root<Employee> emp = c.from(Employee.class);
Join<Employee,Department> dept = emp.join("dept");
c.multiselect(dept.get("name"),emp.get("name"));
c.orderBy(cb.desc(dept.get("name"),cb.asc(emp.get("name"))));
- 9.2.10 GROUP BY 和HAVING子句
CriteriaQuery<Object[]> c = cb.createQuery(Object[].class);
Root<Employee> emp = c.from(Employee.class);
Join<Employee,Project> project = emp.join("projects");
c.multiselect(emp,cb.count(project)).groupBy(emp).having(cb.ge(cb.count(project),2));
- 9.1.1 条件API
9.3 强类型查询定义
9.3.1 元模型API
持久性单元的元模型描述了持久化类型,状态,实体的关系,可嵌入对象以及托管类.通过它,我们可以在运行时查询持久化提供程序,以找出在持久化单元中类的信息.
通过EntityManager接口的getMetamodel()方法访问元模型API.该方法返回一个实现Metamodel接口的对象,我们可以用它来开始导航元模型.Metamodel mm = em.getMetamodel(); EntityType<Employee> emp_ = mm.entity(Employee.class);
对应可嵌入对象和托管类的等效方法分别为embeddable()和managedType().
需要特别注意的是,在此示例中调用entity()不是创建EntityType接口的一个实例,相反,它检索一个元模型对象,当为持久化单元创建EntityManagerFactory时,持久化提供程序已经对其进行了初始化.如果entity()的类参数不是预先存在的持久化类型,那么会抛出一个IllegalArgumentException.
EntityType接口是元模型API中的许多接口之一.包含了持久化类型和特性的信息.9.3.2 强类型的API概述
条件API中基于字符串的API围绕构建路径表达式为中心:join(),fetch(),get()都接受字符串参数.条件API中的强类型API也通过扩展相同的方法来支持路径表达式,但是它也存在于CriteriaBuilder接口的所有方面,并尽可能简化类型化和强制类型安全性.CriteriaQuery<Object> c = cb.createQuery(); Root<Employee> emp = c.from(Employee.class); EntityType<Employee> emp_ = emp.getModel(); MapJoin<Employee,String,Phone> phone = emp.join(emp_getMap("phones",String.class,Phone.class)); c.multiselect(emp.get(emp_.getSingularAttribute("name"),String.class)),phone.key(),phone.value());
- 9.3.3 规范化元模型
迄今为止,已经使用元模型API打开了强类型检查的大门,但是面临着可读性和日益增长的复杂性的代价.元模型API并不复杂,但是它们非常冗长.为了简化它们的使用,JPA2.0规范还定义所谓的持久性单元的规范化模型(canonical metamodel).
@StaticMetamodel(Employee.class)
public class Employee_{
public static volatile SingularAttribute<Employee,Integer> id;
public static volatile SingularAttributre<Employee,String> name;
public static volatile SingularAttributre<Employee,String> salary;
public static volatile SingularAttributre<Employee,Department> dept;
public static volatile SingularAttributre<Employee,Address> address;
public static volatile SingularAttributre<Employee,Project> project;
public static volatile MapAttributre<Employee,String,phone> phones;
}
这个类使用@StaticMetamodel对每个规范化元模型类进行注解,其中标识了它正在建模的持久化类.
- 使用规范化元模型
CriteriaQuery<Object> c = cb.createQuery();
Root<Employee> emp = c.from(Employee.class);
MapJoin<Employee,String,Phone> phone = emp.join(Employee_.phones);
c.multiselect(emp.get(Employee_.name),phone.key(),phone.value());
强类型查询
CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
Root<Employee> emp = c.from(Employee.class);
Subquery<Department> sq = c.subquery(Department.class);
Root<Department> dept = sq.from(Department.class);
Join<Employee,Project> project = dept.join(Department_.employees).join(EMployee_.projects);
sq.select(dept).distinct(true).where(cb.like(project.get(Project_.name),"QA%"));
c.select(emp).where(cb.in(emp.get(Employee_.dept)).value(sq));
- 使用maven自动生成规范化元模型
在pom.xml中引入
<!-- 在相应目录下生成实体类的元模型 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<version>5.1.0.Final</version>
</dependency>
在pom.xml中设置maven-compiler-plugin插件
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<compilerArguments>
<processor>org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor</processor>
</compilerArguments>
</configuration>
</plugin>
</plugins>
</build>
运行mvn compile
,在target/generated-sources/
文件夹下和实体类包名相同的包下边就会生成元模型类
- 使用规范化元模型