您的位置:首页 > 编程语言 > Java开发

JPA criteria 查询:类型安全与面向对象

2016-02-16 13:21 489 查看


序言

自工作以来,除了以前比较流量的hibernate,就是一直使用ORM规范JPA了.而这几天工作需要,研究了下JPA的标准查询,名为:JPAcriteria查询.相比JPQL,其优势是类型安全,更加的面向对象.

使用标准查询,开发人员可在编译的时候就检查查询的正确与否.而以前也只是在Hibernate中听说有过.具体不详,没用过.


JPA元模型概念,及使用

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

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

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

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

01
@Entity
02
@Table
03
public
class
Employee{
04
private
int
id;
05
private
String
name;
06
private
int
age;
07
@OneToMany
08
private
List<Address>
addresses;
09
//
Othercode…
10
}
Employee类(com.demo.entities包中定义)的标准元模型类的名字将是使用javax.persistence.StaticMetamodel注解的Employee_。元模型类的属性全部是static和public的。Employee的每一个属性都会使用在JPA2规范中描述的以下规则在相应的元模型类中映射:

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

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

01
import
javax.annotation.Generated;
02
import
javax.persistence.metamodel.SingularAttribute;
03
import
javax.persistence.metamodel.ListAttribute;
04
import
javax.persistence.metamodel.StaticMetamodel;
05
@Generated
(
"org.hibernate.jpamodelgen.JPAMetaModelEntityProcesso"
)
06
@StaticMetamodel
(Employee.
class
)
07
public
class
Employee_
{
08
public
static
volatile
SingularAttribute<Employee,
Integer>id;
09
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查询

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

01
//All
NecessaryImports
02
@StaticMetamodel
(Dept.
class
)
03
public
class
Dept_
{
04
public
static
volatile
SingularAttribute<Dept,
Integer>id;
05
public
static
volatile
ListAttribute<Dept,
Employee>employeeCollection;
06
public
static
volatile
SingularAttribute<Dept,
String>name;
07
}
08
//All
NecessaryImports
09
@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*FROMemployeeWHEREage>24


构建CriteriaQuery实例API说明


CroteriaQuery

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
);


QueryRoot

AbstractQuery是CriteriaQuery接口的父类。它提供得到查询根的方法。

Criteria查询的查询根定义了实体类型,能为将来导航获得想要的结果,它与SQL查询中的FROM子句类似。

Root实例也是类型化的,且定义了查询的FROM子句中能够出现的类型。

查询根实例能通过传入一个实体类型给AbstractQuery.from方法获得。

Criteria查询,可以有多个查询根。

Employee实体的查询根对象可以用以下的语法获得:

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


过滤Queries

过滤条件应用到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,orandnot方法构建。

下面的代码片段展示了Predicate实例检查年龄大于24岁的员工实例:

1
Predicate
condition=criteriaBuilder.gt(employee.get(Employee_.age),
24
);
2
criteriaQuery.where(condition);
过Employee_元模型类age属性,称之为路径表达式。若age属性与String文本比较,编译器会抛出错误,这在JPQL中是不可能的。


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

当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
Metamodel
metamodel=em.getMetamodel();EntityType<Employee>
2
Employee_
=metamodel.entity(Employee.
class
);
3
Root<Employee>
empRoot=criteriaQuery.from(Employee_);
也有可能调用Root.getModel方法获得元模型信息。类型EntityType<Dept>的实例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"
);


Expression

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*FROMemployeeWHEREagein(20,24)


复合谓词

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

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


连接查询

在SQL中,连接跨多张表以获取查询结果,类似的实体连接通过调用From.join执行,连接帮助从一个实体导航到另一个实体以获得查询结果。

Root的join方法返回一个Join<Dept,Employee>类型(也可以是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*FROMdeptd,employee
eWHEREd.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)); 


参数化表达式

在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*FROMEmployeeWHEREage=
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*FROMEmployeeORDERBYageASC


分组

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

CriteriaQuery<Tuple>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(*)FROMemployeeGROUPBYnameHAVINGnamelike'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:SELECTname,ageFROMemployee<spanstyle=
"white-space:
normal;"
> </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:
SELECTname,ageFROMemployee


返回元组(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:SELECTname,ageFROMemployee


结论

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

====================================================================

转载地址:http://blog.csdn.net/dracotianlong/article/details/28445725
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  jpa java