JPA criteria 查詢:類型安全與面向對象

序言

自工做以來,除了之前比較流量的hibernate,就是一直使用ORM 規範 JPA了.而這幾天工做須要,研究了下JPA的標準查詢,名爲:JPA criteria查詢.相比JPQL,其優點是類型安全,更加的面向對象. html

使用標準查詢,開發人員可在編譯的時候就檢查查詢的正確與否.而之前也只是在Hibernate中據說有過.具體不詳,沒用過. java

用的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元模型概念,及使用

在JPA中,標準查詢是以元模型的概念爲基礎的.元模型是爲具體持久化單元的受管實體定義的.這些實體能夠是實體類,嵌入類或者映射的父類.提供受管實體元信息的類就是元模型類. 數據庫

描述受管類的狀態和他們之間的關係的靜態元模型類能夠 apache

  • 1.從註解處理器產生
  • 2.從程序產生
  • 3.用EntityManager訪問.

以下code,一個簡單的實體類package com.demo.entities;下,實體類Employee ,假設該實體有諸如id,name和age的基本屬性,還有與類Address的OneToMany關聯: 數組

@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規範中描述的如下規則在相應的元模型類中映射: 安全

  • 諸如id,name和age的非集合類型,會定義靜態屬性SingularAttribute<A, B> b,這裏b是定義在類A中的類型爲B的一個對象。
  • 對於Addess這樣的集合類型,會定義靜態屬性ListAttribute<A, B> b,這裏List對象b是定義在類A中類型B的對象。其它集合類型能夠是SetAttribute, MapAttribute 或 CollectionAttribute 類型。

 如下是用註解處理器產生的元模型類package com.demo.entities;下: maven

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 查詢更加類型安全. fetch

元模型API與Java中的標準反射API密切相關。主要不一樣在於使用標準反射API編譯器沒法驗證其正確性。例如:下面的代碼會經過編譯測試:

Class myClass = Class.forName("com.demo.Test");
Field myField = myClass.getField("myName");
編譯器假定com.demo.Test中定義了屬性myName,一旦該類並無定義屬性myName,編譯器將拋出運行時異常。

元模型API會強制編譯器檢查適當的值是否分配給實體類的持久屬性。例如:考慮Employee類的age屬性,它是Integer變量。若該屬性被賦值爲String類型的值,編譯器會拋出錯誤。該實現並不要求支持非標準特性。程序員編寫的元模型類一般稱爲非標準元模型類。當EntityManagerFactory 建立時,持久化提供者會初始化元模型類的屬性。

二.使用criteria 查詢簡單Demo

爲了更好的理解criteria 查詢,考慮擁有Employee實例集合的Dept實體,Employee和Dept的元模型類的代碼以下:

//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;
}
下面的代碼片斷展現了一個criteria 查詢,它用於獲取全部年齡大於24歲的員工:
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 實例API說明

1.CriteriaBuilder 安全查詢建立工廠,建立CriteriaQuery,建立查詢具體具體條件Predicate 等

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 對象如下面的方式建立:

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

3.Root 定義查詢的From子句中能出現的類型

AbstractQuery是CriteriaQuery 接口的父類。它提供獲得查詢根的方法。
Criteria查詢的查詢根定義了實體類型,能爲未來導航得到想要的結果,它與SQL查詢中的FROM子句相似。
Root實例也是類型化的,且定義了查詢的FROM子句中可以出現的類型。
查詢根實例能經過傳入一個實體類型給 AbstractQuery.from方法得到。
Criteria查詢,能夠有多個查詢根。
Employee實體的查詢根對象能夠用如下的語法得到 : 
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歲的員工實例:
Predicate condition = criteriaBuilder.gt(employee.get(Employee_.age), 24);
criteriaQuery.where(condition);

過Employee_元模型類age屬性,稱之爲路徑表達式。若age屬性與String文本比較,編譯器會拋出錯誤,這在JPQL中是不可能的。

5.Predicate[] 多個過濾條件

List<Predicate> predicatesList = new ArrayList<Predicate>();

predicatesList.add(.....Pridicate....)

criteriaQuery.where(predicatesList.toArray(new Predicate[predicatesList.size()]));

OR語句


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


忽略大小寫(全大寫)


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時,查詢就會獲得執行 
TypedQuery<Employee> typedQuery = em.createQuery(criteriaQuery);
List<Employee> result = typedQuery.getResultList();
元模型實例經過調用 EntityManager.getMetamodel 方法得到,EntityType<Employee>的元模型實例經過調用Metamodel.entity(Employee.class)而得到,其被傳入 CriteriaQuery.from 得到查詢根。

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文本進行比較:

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

7.Expression 用在查詢語句的select,where和having子句中,該接口有 isNull, isNotNull 和 in方法

Expression對象用在查詢語句的select,where和having子句中,該接口有 isNull, isNotNull 和 in方法,下面的代碼片斷展現了Expression.in的用法,employye的年齡檢查在20或24的。
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)

下面也是一個更貼切的例子:

//定義一個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()]));

8.複合謂詞

Criteria Query也容許開發者編寫複合謂詞,經過該查詢能夠爲多條件測試下面的查詢檢查兩個條件。首先,name屬性是否以M開頭,其次,employee的age屬性是不是25。邏輯操做符and執行得到結果記錄。
criteriaQuery.where(
 criteriaBuilder.and(
  criteriaBuilder.like(employee.get(Employee_.name), "M%"), 
  criteriaBuilder.equal(employee.get(Employee_.age), 25)
));
em.createQuery(criteriaQuery).getResultList();

鏈接查詢

在SQL中,鏈接跨多張表以獲取查詢結果,相似的實體鏈接經過調用 From.join 執行,鏈接幫助從一個實體導航到另外一個實體以得到查詢結果。
Root的join方法返回一個 Join<Dept, Employee>類型(也能夠是SetJoin,,ListJoin,MapJoin 或者 CollectionJoin類型)。

默認狀況下,鏈接操做使用內鏈接,而外鏈接能夠經過在join方法中指定JoinType參數爲LEFT或RIGHT來實現。

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

抓取鏈接

當涉及到collection屬性時,抓取鏈接對優化數據訪問是很是有幫助的。這是經過預抓取關聯對象和減小懶加載開銷而達到的。
使用 criteria 查詢,fetch方法用於指定關聯屬性
Fetch鏈接的語義與Join是同樣的,由於Fetch操做不返回Path對象,因此它不能未來在查詢中引用。
在如下例子中,查詢Dept對象時employeeCollection對象被加載,這不會有第二次查詢數據庫,由於有懶加載。
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

路徑表達式

Root實例,Join實例或者從另外一個Path對象的get方法得到的對象使用get方法能夠獲得Path對象,當查詢須要導航到實體的屬性時,路徑表達式是必要的。
Get方法接收的參數是在實體元模型類中指定的屬性。
Path對象通常用於Criteria查詢對象的select或where方法。例子以下:
CriteriaQuery<String> criteriaQuery = criteriaBuilder.createQuery(String.class);
Root<Dept> root = criteriaQuery.from(Dept.class);
criteriaQuery.select(root.get(Dept_.name));&nbsp;

參數化表達式

     在JPQL中,查詢參數是在運行時經過使用命名參數語法(冒號加變量,如 :age)傳入的。在Criteria查詢中,查詢參數是在運行時建立ParameterExpression對象併爲在查詢前調用TypeQuery,setParameter方法設置而傳入的。下面代碼片斷展現了類型爲Integer的ParameterExpression age,它被設置爲24:
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;

排序結果

     Criteria查詢的結果能調用CriteriaQuery.orderBy方法排序,該方法接收一個Order對象作爲參數。經過調用  CriteriaBuilder.asc 或 CriteriaBuilder.Desc,Order對象能被建立。如下代碼片斷中,Employee實例是基於age的升序排列。 
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();
  對應  SQL: SELECT * FROM Employee ORDER BY age ASC

分組

CriteriaQuery 實例的groupBy 方法用於基於Expression的結果分組。查詢經過設置額外表達式,之後調用having方法。下面代碼片斷中,查詢按照Employee類的name屬性分組,且結果以字母N開頭:
CriteriaQuery<Tuple> cq = criteriaBuilder.createQuery(Tuple.class);
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();
對應  SQL:    SELECT name, COUNT(*) FROM employeeGROUP BY name HAVING name like 'N%'

查詢投影

Criteria查詢的結果與在Critiria查詢建立中指定的同樣。結果也能經過把查詢根傳入 CriteriaQuery.select中顯式指定。Criteria查詢也給開發者投影各類結果的能力。

使用construct()

使用該方法,查詢結果能由非實體類型組成。在下面的代碼片斷中,爲EmployeeDetail類建立了一個Criteria查詢對象,而EmployeeDetail類並非實體類型。
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;">&nbsp;</span>

返回Object[]的查詢

Criteria查詢也能經過設置值給CriteriaBuilder.array方法返回Object[]的結果。下面的代碼片斷中,數組大小是2(由String和Integer組成)。
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();
對應  SQL: SELECT name, age FROM employee

返回元組(Tuple)的查詢

數據庫中的一行數據或單個記錄一般稱爲元組。經過調用CriteriaBuilder.createTupleQuery()方法,查詢能夠用於元組上。CriteriaQuery.multiselect方法傳入參數,它必須在查詢中返回。
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();
對應 SQL: SELECT name, age FROM employee

結論

     Criteria查詢是一種以更加面向對象的方式查詢數據庫的方法、在本文中,我討論了JPA2中類型安全的Criteria查詢,以及對於理解Criteria查詢很是重要的元模型的概念。也討論了Criteria查詢中的各類API。

轉的,以爲寫的不錯,整理到一個文章裏了,地址:http://tanlan.iteye.com/blog/1101110

相關文章
相關標籤/搜索