自工作以来,除了以前比较流量的hibernate,就是一直使用ORM 规范 JPA了.而这几天工作需要,研究了下JPA的标准查询,名为:JPA criteria查询.相比JPQL,其优势是类型安全,更加的面向对象.
使用标准查询,开发人员可在编译的时候就检查查询的正确与否.而以前也只是在Hibernate中听说有过.具体不详,没用过.
用的maven插件生成的.具体看这些把.
Hibernate
org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor
http://relation.to/Bloggers/HibernateStaticMetamodelGeneratorAnnotationProcessor
OpenJPA
org.apache.openjpa.persistence.meta.AnnotationProcessor6
http://openjpa.apache.org/builds/latest/docs/manual/manual.html#d0e11094
DataNucleus
org.datanucleus.jpa.JPACriteriaProcessor
http://www.datanucleus.org/products/accessplatform_2_1/jpa/jpql_criteria_metamodel.html
在JPA中,标准查询是以元模型的概念为基础的.元模型是为具体持久化单元的受管实体定义的.这些实体可以是实体类,嵌入类或者映射的父类.提供受管实体元信息的类就是元模型类.
描述受管类的状态和他们之间的关系的静态元模型类可以
如下code,一个简单的实体类package com.demo.entities;下,实体类Employee ,假设该实体有诸如id,name和age的基本属性,还有与类Address的OneToMany关联:
1
2
3
4
5
6
7
8
9
10
|
@Entity
@Table
public
class
Employee{
private
int
id;
private
String name;
private
int
age;
@OneToMany
private
List<Address> addresses;
// Other code…
}
|
Employee类(com.demo.entities包中定义)的标准元模型类的名字将是使用 javax.persistence.StaticMetamodel注解的Employee_。元模型类的属性全部是static和public的。Employee的每一个属性都会使用在JPA2规范中描述的以下规则在相应的元模型类中映射:
以下是用注解处理器产生的元模型类package com.demo.entities;下:
1
2
3
4
5
6
7
8
9
10
11
12
|
import
javax.annotation.Generated;
import
javax.persistence.metamodel.SingularAttribute;
import
javax.persistence.metamodel.ListAttribute;
import
javax.persistence.metamodel.StaticMetamodel;
@Generated
(
"org.hibernate.jpamodelgen.JPAMetaModelEntityProcesso"
)
@StaticMetamodel
(Employee.
class
)
public
class
Employee_ {
public
static
volatile
SingularAttribute<Employee, Integer> id;
public
static
volatile
SingularAttribute<Employee, Integer> age;
public
static
volatile
SingularAttribute<Employee, String> name;
public
static
volatile
ListAttribute<Employee, Address> addresses;
}
|
就像它的名字表明的,注解处理器处理注解,帮助产生源代码。注解处理在编译时就能激活。元模型类遵循JPA2.0规范中为定义标准元模型类而描述的规则创建。
使用元模型类最大的优势是凭借其实例化可以在编译时访问实体的持久属性.该特性使得criteria 查询更加类型安全.
元模型API与Java中的标准反射API密切相关。主要不同在于使用标准反射API编译器无法验证其正确性。例如:下面的代码会通过编译测试:
1
2
|
Class myClass = Class.forName(
"com.demo.Test"
);
Field myField = myClass.getField(
"myName"
);
|
元模型API会强制编译器检查适当的值是否分配给实体类的持久属性。例如:考虑Employee类的age属性,它是Integer变量。若该属性被赋值为String类型的值,编译器会抛出错误。该实现并不要求支持非标准特性。程序员编写的元模型类通常称为非标准元模型类。当EntityManagerFactory 创建时,持久化提供者会初始化元模型类的属性。
为了更好的理解criteria 查询,考虑拥有Employee实例集合的Dept实体,Employee和Dept的元模型类的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
//All Necessary Imports
@StaticMetamodel
(Dept.
class
)
public
class
Dept_ {
public
static
volatile
SingularAttribute<Dept, Integer> id;
public
static
volatile
ListAttribute<Dept, Employee> employeeCollection;
public
static
volatile
SingularAttribute<Dept, String> name;
}
//All Necessary Imports
@StaticMetamodel
(Employee.
class
)
public
class
Employee_ {
public
static
volatile
SingularAttribute<Employee, Integer> id;
public
static
volatile
SingularAttribute<Employee, Integer> age;
public
static
volatile
SingularAttribute<Employee, String> name;
public
static
volatile
SingularAttribute<Employee, Dept> deptId;
}
|
1
2
3
4
5
6
7
|
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<Employee> criteriaQuery = criteriaBuilder.createQuery(Employee.
class
);
Root<Employee> employee = criteriaQuery.from(Employee.
class
);
Predicate condition = criteriaBuilder.gt(employee.get(Employee_.age),
24
);
criteriaQuery.where(condition);
TypedQuery<Employee> typedQuery = em.createQuery(criteriaQuery);
List<Employee> result = typedQuery.getResultList();
|
对应的SQL: SELECT * FROM employee WHERE age > 24
CriteriaQuery对象必须在实体类型或嵌入式类型上的Criteria 查询上起作用。
它通过调用 CriteriaBuilder, createQuery 或CriteriaBuilder.createTupleQuery 获得。
CriteriaBuilder就像CriteriaQuery 的工厂一样。
CriteriaBuilder工厂类是调用EntityManager.getCriteriaBuilder 或 EntityManagerFactory.getCriteriaBuilder而得。
Employee实体的 CriteriaQuery 对象以下面的方式创建:
1
2
|
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<Employee> criteriaQuery = criteriaBuilder.createQuery(Employee.
class
);
|
1
|
Root<Employee> employee = criteriaQuery.from(Employee.
class
);
|
1
2
|
Predicate condition = criteriaBuilder.gt(employee.get(Employee_.age),
24
);
criteriaQuery.where(condition);
|
过Employee_元模型类age属性,称之为路径表达式。若age属性与String文本比较,编译器会抛出错误,这在JPQL中是不可能的。
List<Predicate> predicatesList = new ArrayList<Predicate>();
predicatesList.add(.....Pridicate....)
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)) +
"%"
));
|
注意,你使用EntityManager创建查询时,可以在输入中指定一个CriteriaQuery对象,它返回一个TypedQuery,它是JPA 2.0引入javax.persistence.Query接口的一个扩展,TypedQuery接口知道它返回的类型。
所以使用中,先创建查询得到TypedQuery,然后通过typeQuery得到结果.
当EntityManager.createQuery(CriteriaQuery)方法调用时,一个可执行的查询实例会创建,该方法返回指定从 criteria 查询返回的实际类型的TypedQuery 对象。
TypedQuery 接口是javax.persistence.Queryinterface.的子类型。在该片段中, TypedQuery 中指定的类型信息是Employee,调用getResultList时,查询就会得到执行
TypedQuery<Employee> typedQuery = em.createQuery(criteriaQuery);
List<Employee> result = typedQuery.getResultList();
元模型实例通过调用 EntityManager.getMetamodel 方法获得,EntityType<Employee>的元模型实例通过调用Metamodel.entity(Employee.class)而获得,其被传入 CriteriaQuery.from 获得查询根。
1
2
3
|
Metamodel metamodel = em.getMetamodel();EntityType<Employee>
Employee_ = metamodel.entity(Employee.
class
);
Root<Employee> empRoot = criteriaQuery.from(Employee_);
|
也有可能调用Root.getModel方法获得元模型信息。类型 EntityType<Dept>的实例Dept_和name属性可以调用getSingularAttribute 方法获得,它与String文本进行比较:
1
2
3
4
|
CriteriaQuery criteriaQuery = criteriaBuilder.createQuery();
Root<Dept> dept = criteriaQuery.from(Dept.
class
);
EntityType<Dept> Dept_ = dept.getModel();
Predicate testCondition = criteriaBuilder.equal(dept.get(Dept_.getSingularAttribute(
"name"
, String.
class
)),
"Ecomm"
);
|
1
2
3
4
|
CriteriaQuery<Employee> criteriaQuery = criteriaBuilder .createQuery(Employee.
class
);
Root<Employee> employee = criteriaQuery.from(Employee.
class
);
criteriaQuery.where(employee.get(Employee_.age).in(
20
,
24
));
em.createQuery(criteriaQuery).getResultList();
|
对应的 SQL: SELECT * FROM employee WHERE age in (20, 24)
下面也是一个更贴切的例子:
1
2
3
4
5
6
7
8
9
|
//定义一个Expression
Expression<String> exp = root.get(Employee.id);
//
List<String> strList=
new
ArrayList<>();
strList.add(
"20"
);
strList.add(
"24"
);
predicatesList.add(exp.in(strList));
criteriaQuery.where(predicatesList.toArray(
new
Predicate[predicatesList.size()]));
|
1
2
3
4
5
6
|
criteriaQuery.where(
criteriaBuilder.and(
criteriaBuilder.like(employee.get(Employee_.name),
"M%"
),
criteriaBuilder.equal(employee.get(Employee_.age),
25
)
));
em.createQuery(criteriaQuery).getResultList();
|
默认情况下,连接操作使用内连接,而外连接可以通过在join方法中指定JoinType参数为LEFT或RIGHT来实现。
1
2
3
4
5
|
CriteriaQuery<Dept> cqDept = criteriaBuilder.createQuery(Dept.
class
);
Root<Dept> deptRoot = cqDept.from(Dept.
class
);
Join<Dept, Employee> employeeJoin = deptRoot.join(Dept_.employeeCollection);
cqDept.where(criteriaBuilder.equal(employeeJoin.get(Employee_.deptId).get(Dept_.id),
1
));
TypedQuery<Dept> resultDept = em.createQuery(cqDept);
|
1
2
3
4
5
|
CriteriaQuery<Dept> d = cb.createQuery(Dept.
class
);
Root<Dept> deptRoot = d.from(Dept.
class
);
deptRoot.fetch(
"employeeCollection"
, JoinType.LEFT);
d.select(deptRoot);
List<Dept> dList = em.createQuery(d).getResultList();
|
对应SQL: SELECT * FROM dept d, employee e WHERE d.id = e.deptId
1
2
3
|
CriteriaQuery<String> criteriaQuery = criteriaBuilder.createQuery(String.
class
);
Root<Dept> root = criteriaQuery.from(Dept.
class
);
criteriaQuery.select(root.get(Dept_.name));
|
1
2
3
4
5
6
|
ParameterExpression<Integer> age = criteriaBuilder.parameter(Integer.
class
);
Predicate condition = criteriaBuilder.gt(testEmp.get(Employee_.age), age);
criteriaQuery.where(condition);
TypedQuery<Employee> testQuery = em.createQuery(criteriaQuery);
List<Employee> result = testQuery.setParameter(age,
24
).getResultList();
Corresponding SQL: SELECT * FROM Employee WHERE age =
24
;
|
1
2
3
4
|
CriteriaQuery<Employee> criteriaQuery = criteriaBuilder .createQuery(Employee.
class
);
Root<Employee> employee = criteriaQuery.from(Employee.
class
);
criteriaQuery.orderBy(criteriaBuilder.asc(employee.get(Employee_.age)));
em.createQuery(criteriaQuery).getResultList();
|
1
2
3
4
5
6
|
Root<Employee> employee = cq.from(Employee.
class
);
cq.groupBy(employee.get(Employee_.name));
cq.having(criteriaBuilder.like(employee.get(Employee_.name),
"N%"
));
cq.select(criteriaBuilder.tuple(employee.get(Employee_.name),criteriaBuilder.count(employee)));
TypedQuery<Tuple> q = em.createQuery(cq);
List<Tuple> result = q.getResultList();
|
1
2
3
4
5
|
CriteriaQuery<EmployeeDetails> criteriaQuery = criteriaBuilder.createQuery(EmployeeDetails.
class
);
Root<Employee> employee = criteriaQuery.from(Employee.
class
);
criteriaQuery.select(criteriaBuilder.construct(EmployeeDetails.
class
, employee.get(Employee_.name), employee.get(Employee_.age)));
em.createQuery(criteriaQuery).getResultList();
Corresponding SQL: SELECT name, age FROM employee<span style=
"white-space: normal;"
> </span>
|
1
2
3
4
|
CriteriaQuery<Object[]> criteriaQuery = criteriaBuilder.createQuery(Object[].
class
);
Root<Employee> employee = criteriaQuery.from(Employee.
class
);
criteriaQuery.select(criteriaBuilder.array(employee.get(Employee_.name), employee.get(Employee_.age)));
em.createQuery(criteriaQuery).getResultList();
|
1
2
3
4
|
CriteriaQuery<Tuple> criteriaQuery = criteriaBuilder.createTupleQuery();
Root<Employee> employee = criteriaQuery.from(Employee.
class
);
criteriaQuery.multiselect(employee.get(Employee_.name).alias(
"name"
), employee.get(Employee_.age).alias(
"age"
));
em.createQuery(criteriaQuery).getResultList();
|