Java EE 6引入的JPA 2.0四大新特性詳解
原創(chuàng)【51CTO精選譯文】Java EE 5平臺引入了Java持久化API(Java Persistence API,JPA),它為Java EE和Java SE應(yīng)用程序提供了一個(gè)基于POJO的持久化模塊。JPA處理關(guān)系數(shù)據(jù)與Java對象之間的映射,它使對象/關(guān)系(O/R)映射標(biāo)準(zhǔn)化,JPA已經(jīng)被廣泛采用,已經(jīng)成為事實(shí)上的O/R持久化企業(yè)標(biāo)準(zhǔn)。
Java EE 6帶來了JPA的最新版本 — JSR 317:Java持久化2.0,JPA 2.0帶來了許多新特性和增強(qiáng),包括:
1、對象/關(guān)系映射增強(qiáng);
2、Java持久化查詢語言增強(qiáng);
3、一種新的基于標(biāo)準(zhǔn)的查詢API;
4、支持悲觀鎖定。
對象/關(guān)系映射增強(qiáng)
JPA 1.0支持集合的映射,但這些集合只能包含實(shí)體,JPA 2.0增加了集合映射的基礎(chǔ)數(shù)據(jù)類型,如String和Integer,以及嵌入式對象的集合。JPA中的嵌入式對象是一個(gè)不能存在于它自身的對象,而是作為父對象的一部分存在,即它的數(shù)據(jù)不是存在于它自己的表中,而是嵌入在父對象的表中。
JPA 2.0增加了兩個(gè)支持新的集合映射的注解:@ElementCollection 和 @CollectionTable。使用@ElementCollection注解指定集合的嵌入式對象,這些集合是獨(dú)立存儲在集合表中的,使用@CollectionTable注解指定集合表的詳細(xì)信息,如它包含的列。
下面是一個(gè)嵌入式類,表示了車輛的訪問服務(wù),它存儲了訪問的日期,描述和費(fèi)用,此外,車輛可以配備一或多個(gè)可選功能,每個(gè)功能是FeatureType類型的一個(gè)枚舉值。
- public enum FeatureType { AC, CRUISE, PWR, BLUETOOTH, TV, ... }
- @Embeddable
- public class ServiceVisit {
- @Temporal(DATE)
- @Column(name="SVC_DATE")
- Date serviceDate;
- String workDesc;
- int cost;
- }
枚舉值和嵌入式對象可以在一個(gè)表示車輛服務(wù)歷史的實(shí)體中使用,如:
- @Entity
- public class Vehicle {
- @Id int vin;
- @ElementCollection
- @CollectionTable(name="VEH_OPTNS")
- . @Column(name="FEAT")
- Set<FeatureType> optionalFeatures;
- @ElementCollection
- @CollectionTable(name="VEH_SVC")
- @OrderBy("serviceDate")
- List<ServiceVisit> serviceHistory;
- ...
- }
#t#Vehicle實(shí)體中的第一對注解@ElementCollection 和 @CollectionTable指定FeatureType值存儲在VEH_OPTNS集合表中,第二對注解@ElementCollection 和 @CollectionTable指定ServiceVisit嵌入式對象存儲在VEH_SVC集合表中。
雖然在例子中沒有顯示,@ElementCollection注解有兩個(gè)屬性:targetClass 和 fetch。targetClass屬性指定基礎(chǔ)類或嵌入式類的類名,如果字段或?qū)傩允鞘褂梅盒投x的,那這兩個(gè)屬性是可選的,上面這個(gè)例子就是這樣。Fetch屬性是可選的,它指定集合是延后檢索還是立即檢索,使用javax.persistence.FetchType常量,值分別用LAZY和EAGER,默認(rèn)情況下,集合是延后匹配的。
JPA 2.0中還有其它許多關(guān)于對象/關(guān)系映射的增強(qiáng),例如,JPA 2.0支持嵌套式嵌入,關(guān)系嵌入和有序列表,也增加了新的注解增強(qiáng)映射功能,通過@Access注解更靈活地支持特定的訪問類型,更多用于實(shí)體關(guān)系的映射選項(xiàng),如對單向一對多關(guān)系的外鍵映射支持,通過@MapsId注解支持派生身份,支持孤體刪除。
#p#
Java持久化查詢語言增強(qiáng)
JPA 1.0定義了一個(gè)廣泛的Java持久化查詢語言(非正式簡稱為JPQL。有關(guān)JPQL查詢的缺陷,可參考51CTO之前發(fā)布的這篇文章),使用它你可以查詢實(shí)體和它們的持久化狀態(tài)。JPA 2.0對JPQL做了大量改進(jìn),如現(xiàn)在可以在查詢中使用case表達(dá)式。在下面的查詢中,如果雇員的評分為1,則通過乘以1.1對雇員的薪水進(jìn)行了增長,如果評分為2,則乘以1.05,其它評分則乘以1.01。
- UPDATE Employee e
- SET e.salary =
- CASE WHEN e.rating = 1 THEN e.salary * 1.1
- WHEN e.rating = 2 THEN e.salary * 1.05
- ELSE e.salary * 1.01
- END
JPA 2.0也為JPQL增加了大量新的運(yùn)算符,如NULLIF和COALESCE,當(dāng)數(shù)據(jù)庫使用其它非空數(shù)據(jù)解碼時(shí),NULLIF運(yùn)算符是非常有用的,使用NULLIF,你可以在查詢中將這些值轉(zhuǎn)換為空值,如果參數(shù)等于NULLIF,NULLIF會(huì)返回空值,否則返回第一個(gè)參數(shù)的值。
假設(shè)薪水?dāng)?shù)據(jù)保存在employee表中,數(shù)據(jù)類型為整數(shù),卻掉的薪水解碼為-9999,下面的查詢返回薪水的平均值,為了正確地忽略卻掉的薪水,查詢使用NULLIF將-9999轉(zhuǎn)換為空值。
- SELECT AVG(NULLIF(e.salary, -99999))
- FROM Employee e
COALESCE運(yùn)算符接收一串參數(shù),從列表中返回第一個(gè)非空值,相當(dāng)于下面的case表達(dá)式:
- CASE WHEN value1 IS NOT NULL THEN value1
- WHEN value2 IS NOT NULL THEN value2
- WHEN value3 IS NOT NULL THEN value3
- ...
- ELSE NULL
- END
假設(shè)employee表包括一個(gè)辦公電話號碼和家庭電話號碼列,無電話號碼的列使用空值表示。下面的查詢返回每個(gè)雇員的姓名和電話號碼,COALESCE運(yùn)算符指定查詢返回辦公電話號碼,但如果為空,則返回家庭電話號碼,如果兩者都為空,則返回一個(gè)空值。
- SELECT Name, COALESCE(e.work_phone, e.home_phone) phone
- FROM Employee e
#t#JPA 2.0向JPQL增加的其它運(yùn)算符是INDEX,TYPE,KEY,VALUE和ENTRY。INDEX運(yùn)算符指定查詢時(shí)的排序順序,TYPE運(yùn)算符選擇一個(gè)實(shí)體的類型,將查詢限制到一或多個(gè)實(shí)體類型,KEY,VALUE和ENTRY運(yùn)算符是JPA 2.0中的泛化映射功能的一部分。使用KEY運(yùn)算符提取映射鍵,VALUE運(yùn)算符提取映射值,ENTRY運(yùn)算符選擇一個(gè)映射實(shí)體。
此外,JPA 2.0增加了選擇列表、以及集合值參數(shù)和非多態(tài)查詢中運(yùn)算符的支持。
#p#
標(biāo)準(zhǔn)的API
JPA 2.0中引入的另一個(gè)重要特性是標(biāo)準(zhǔn)的API,利用這個(gè)API可以動(dòng)態(tài)地構(gòu)建基于對象的查詢,本質(zhì)上,這個(gè)標(biāo)準(zhǔn)API等價(jià)于面向?qū)ο蟮腏PQL,使用它,你可以使用基于對象的方法創(chuàng)建查詢,不再需要JPQL使用的字符串操作。
標(biāo)準(zhǔn)API是基于元模型的,元模型是一個(gè)提供了架構(gòu)級關(guān)于持久化單元托管類的元數(shù)據(jù)的抽象模型, 元模型讓你構(gòu)建強(qiáng)類型的查詢,它也允許你瀏覽持久化單元的邏輯結(jié)構(gòu)。
通常,一個(gè)注解處理器使用元模型生成靜態(tài)元模型類,這些類提供持久化狀態(tài)和持久化單元托管類的關(guān)系,但你可以對靜態(tài)元模型類編碼。下面是一個(gè)實(shí)體實(shí)例:
- @Entity public class Employee {
- @Id Long Id;
- String firstName;
- String lastName;
- Department dept;
- }
下面是對應(yīng)的靜態(tài)元模型類:
- import javax.persistence.meta,model.SingularAttribute;
- import javax.persistence.meta,model.StaticMetamodel;
- @Generated("EclipseLink JPA 2.0 Canonical Model Generation"
- @StaticMetamodel(Employee.class)
- public class Employee_ {
- public static volatile SingularAttribute<Employee, Long> id;
- public static volatile SingularAttribute<Employee, String> firstName;
- public static volatile SingularAttribute<Employee, String> lastName;
- public static volatile SingularAttribute<Employee, Department> dept;
- }
此外,JPA 2.0元模型API允許你動(dòng)態(tài)訪問元模型,因此當(dāng)你使用標(biāo)準(zhǔn)API時(shí),既可以靜態(tài)訪問元模型類,也可以動(dòng)態(tài)訪問元模型類。標(biāo)準(zhǔn)API提供了更好的靈活性,既可以使用基于對象的方法,又可以使用基于字符串的方法導(dǎo)航元模型,這意味著你有四種使用標(biāo)準(zhǔn)API的方法:
1、靜態(tài)使用元模型類
2、靜態(tài)使用字符串
3、動(dòng)態(tài)使用元模型
4、動(dòng)態(tài)使用字符串
無論你使用哪種方法,通過構(gòu)造一個(gè)CriteriaQuery對象定義一個(gè)基于標(biāo)準(zhǔn)API的查詢時(shí),使用一個(gè)工廠對象CriteriaBuilder構(gòu)造CriteriaQuery,可以從EntityManager 或 EntityManagerFactory類中獲得CriteriaBuilder。下面的代碼構(gòu)造一個(gè)CriteriaQuery對象:
- EntityManager em = ... ;
- CriteriaBuilder cb = em.getCriteriaBuilder();
- CriteriaQuery<Customer> cq = cb.createQuery(Customer.class);
注意CriteriaQuery對象是泛型類型,使用CriteriaBuilder 的createQuery方法創(chuàng)建一個(gè)CriteriaQuery,并為查詢結(jié)果指定類型。在這個(gè)例子中,createQuery 方法的Employee.class參數(shù)指定查詢結(jié)果類型是Employee。CriteriaQuery對象和創(chuàng)建它們的方法是強(qiáng)類型的。
接下來,為CriteriaQuery對象指定一個(gè)或多個(gè)查詢源,查詢源表示查詢基于的實(shí)體。你創(chuàng)建一個(gè)查詢源,然后使用AbstractQuery接口的from()方法將其添加到查詢。AbstractQuery接口是眾多接口中的一員,如CriteriaQuery,F(xiàn)rom和root,它們都定義在標(biāo)準(zhǔn)API中。CriteriaQuery接口繼承AbstractQuery接口的屬性。
from()方法的參數(shù)是實(shí)體類或EntityType實(shí)體的實(shí)例,from()方法的結(jié)果是一個(gè)Root對象,Root接口擴(kuò)展From接口,它表示某個(gè)查詢的from子句中可能出現(xiàn)的對象。
下面的代碼增加一個(gè)查詢源到CriteriaQuery對象:
- CriteriaBuilder cb = em.getCriteriaBuilder();
- CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
- Root<Employee> emp = cq.from(Employee.class);
當(dāng)你向CriteriaQuery對象添加一個(gè)或多個(gè)查詢源后,你訪問元模型,然后構(gòu)造一個(gè)查詢表達(dá)式,你如何做取決于你是以靜態(tài)方式提交查詢還是以動(dòng)態(tài)方式提交查詢,以及是使用元模型還是字符串導(dǎo)航元模型。下面是一個(gè)使用元模型類靜態(tài)查詢的例子:
- cq.select(emp);
- cq.where(cb.equal(emp.get(Employee_.lastName), "Smith"));
- TypedQuery<Employee> query = em.createQuery(cq);
- List<Employee> rows = query.getResultList();
CriteriaQuery接口的select() 和 where()方法指定查詢結(jié)果返回的選擇項(xiàng)目。
注意,你使用EntityManager創(chuàng)建查詢時(shí),可以在輸入中指定一個(gè)CriteriaQuery對象,它返回一個(gè)TypedQuery,它是JPA 2.0引入javax.persistence.Query接口的一個(gè)擴(kuò)展,TypedQuery接口知道它返回的類型。
在元模型術(shù)語中,Employee_是對應(yīng)于Employee實(shí)體類的規(guī)范化元模型類,一個(gè)規(guī)范化元模型類遵循JPA 2.0規(guī)范中描述的某些規(guī)則。例如,元模型類的名字來源于托管類,一般都是在托管類名字后面追加一個(gè)下劃線“_”。一個(gè)規(guī)范化元模型是一個(gè)包含靜態(tài)元模型類的元模型,這個(gè)靜態(tài)元模型對應(yīng)于實(shí)體,映射的超類,以及持久化單元中的嵌入式類。實(shí)際上,這個(gè)查詢使用了規(guī)范化元模型。下面是一個(gè)完整的查詢:
- EntityManager em = ... ;
- CriteriaBuilder cb = em.getCriteriaBuilder();
- CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
- Root<Employee> emp = cq.from(Employee.class);
- cq.select(emp);
- cq.where(cb.equal(emp.get(Employee_.lastName), "Smith"));
- TypedQuery<Employee> query = em.createQuery(cq);
- List<Employee> rows = query.getResultList();
下面是使用元模型API查詢的動(dòng)態(tài)版本:
- EntityManager em = ... ;
- CriteriaBuilder cb = em.getCriteriaBuilder();
- CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
- Root<Employee> emp = cq.from(Employee.class);
- EntityType<Employee> emp_ = emp.getModel();
- cq.select(emp);
- cq.where(cb.equal(emp.get(emp_.getSingularAttribute("lastName", String.class)),"Smith"));
- TypedQuery<Employee> query=em.createQuery(cq);
- List<Employee> rows=query.getResultList();
#t#使用元模型API的標(biāo)準(zhǔn)查詢提供了與使用規(guī)范化元模型相同的類型,但它比基于規(guī)范化元模型的查詢更冗長。
Root的getModel()方法返回根對應(yīng)的元模型實(shí)體,它也允許運(yùn)行時(shí)訪問在Employee實(shí)體中聲明的持久化屬性。
getSingularAttribute()方法是一個(gè)元模型API方法,它返回一個(gè)持久化的單值屬性或字段,在這個(gè)例子中,它返回值為Smith 的lastName屬性。下面是使用字符串的元數(shù)據(jù)導(dǎo)航查詢的靜態(tài)版本:
- EntityManager em = ... ;
- CriteriaBuilder cb = em.getCriteriaBuilder();
- CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
- Root<Employee> emp = cq.from(Employee.class);
- cq.select(emp);
- cq.where(cb.equal(emp.get("lastName"), "Smith"));
- TypedQuery query = em.createQuery(cq);
- List <Employee>rows = query.getResultList();
這個(gè)基于字符串的方法要相對容易使用些,但卻失去了元模型具有的類型安全。
#p#
支持悲觀鎖
鎖是處理數(shù)據(jù)庫事務(wù)并發(fā)的一種技術(shù),當(dāng)兩個(gè)或更多數(shù)據(jù)庫事務(wù)并發(fā)地訪問相同數(shù)據(jù)時(shí),鎖可以保證同一時(shí)間只有一個(gè)事務(wù)可以修改數(shù)據(jù)。
鎖的方法通常有兩種:樂觀鎖和悲觀鎖。樂觀鎖認(rèn)為多個(gè)并發(fā)事務(wù)之間很少出現(xiàn)沖突,也就是說不會(huì)經(jīng)常出現(xiàn)同一時(shí)間讀取或修改相同數(shù)據(jù),在樂觀鎖中,其目標(biāo)是讓并發(fā)事務(wù)自由地同時(shí)得到處理,而不是發(fā)現(xiàn)或預(yù)防沖突。兩個(gè)事務(wù)在同一時(shí)刻可以訪問相同的數(shù)據(jù),但為了預(yù)防沖突,需要對數(shù)據(jù)執(zhí)行一次檢查,檢查自上次讀取數(shù)據(jù)以來發(fā)生的任何變化。
悲觀鎖認(rèn)為事務(wù)會(huì)經(jīng)常發(fā)生沖突,在悲觀鎖中,讀取數(shù)據(jù)的事務(wù)會(huì)鎖定數(shù)據(jù),在前面的事務(wù)提交之前,其它事務(wù)都不能修改數(shù)據(jù)。
JPA 1.0只支持樂觀鎖,你可以使用EntityManager類的lock()方法指定鎖模式的值,可以是READ或WRITE,如:
- EntityManager em = ... ;
- em.lock (p1, READ);
對于READ鎖模式,JPA實(shí)體管理器在事務(wù)提交前都會(huì)鎖定實(shí)體,檢查實(shí)體的版本屬性確定實(shí)體自上次被讀取以來是否有更新,如果版本屬性被更新了,實(shí)體管理器會(huì)拋出一個(gè)OptimisticLockException異常,并回滾事務(wù)。
對于WRITE鎖模式,實(shí)體管理器執(zhí)行和READ鎖模式相同的樂觀鎖操作,但它也會(huì)更新實(shí)體的版本列。
JPA 2.0增加了6種新的鎖模式,其中兩個(gè)是樂觀鎖。JPA 2.0也允許悲觀鎖,并增加了3種悲觀鎖,第6種鎖模式是無鎖。
下面是新增的兩個(gè)樂觀鎖模式:
1、OPTIMISTIC:它和READ鎖模式相同,JPA 2.0仍然支持READ鎖模式,但明確指出在新應(yīng)用程序中推薦使用OPTIMISTIC。
2、OPTIMISTIC_FORCE_INCREMENT:它和WRITE鎖模式相同,JPA 2.0仍然支持WRITE鎖模式,但明確指出在新應(yīng)用程序中推薦使用OPTIMISTIC_FORCE_INCREMENT。
下面是新增的三個(gè)悲觀鎖模式:
1、PESSIMISTIC_READ:只要事務(wù)讀實(shí)體,實(shí)體管理器就鎖定實(shí)體,直到事務(wù)完成鎖才會(huì)解開,當(dāng)你想使用重復(fù)讀語義查詢數(shù)據(jù)時(shí)使用這種鎖模式,換句話說就是,當(dāng)你想確保數(shù)據(jù)在連續(xù)讀期間不被修改,這種鎖模式不會(huì)阻礙其它事務(wù)讀取數(shù)據(jù)。
2、PESSIMISTIC_WRITE:只要事務(wù)更新實(shí)體,實(shí)體管理器就會(huì)鎖定實(shí)體,這種鎖模式強(qiáng)制嘗試修改實(shí)體數(shù)據(jù)的事務(wù)串行化,當(dāng)多個(gè)并發(fā)更新事務(wù)出現(xiàn)更新失敗幾率較高時(shí)使用這種鎖模式。
3、PESSIMISTIC_FORCE_INCREMENT:當(dāng)事務(wù)讀實(shí)體時(shí),實(shí)體管理器就鎖定實(shí)體,當(dāng)事務(wù)結(jié)束時(shí)會(huì)增加實(shí)體的版本屬性,即使實(shí)體沒有修改。
你也可以指定新的鎖模式NONE,在這種情況下表示沒有鎖發(fā)生。
JPA 2.0也提供了多種方法為實(shí)體指定鎖模式,你可以使用EntityManager的lock() 和 find()方法指定鎖模式。此外,EntityManager.refresh()方法可以恢復(fù)實(shí)體實(shí)例的狀態(tài)。
下面的代碼顯示了使用PESSIMISTIC_WRITE鎖模式的悲觀鎖:
- // read
- Part p = em.find(Part.class, pId);
- // lock and refresh before update
- em.refresh(p, PESSIMISTIC_WRITE);
- int pAmount = p.getAmount();
- p.setAmount(pAmount - uCount);
在這個(gè)例子中,它首先讀取一些數(shù)據(jù),然后應(yīng)用PESSIMISTIC_WRITE鎖,在更新數(shù)據(jù)之前調(diào)用EntityManager.refresh()方法,當(dāng)事務(wù)更新實(shí)體時(shí),PESSIMISTIC_WRITE鎖鎖定實(shí)體,其它事務(wù)就不能更新相同的實(shí)體,直到前面的事務(wù)提交。
更多JPA 2.0的新特性
除了前面描述的增強(qiáng)和新特性外,JPA 2.0可以使用Bean驗(yàn)證自動(dòng)驗(yàn)證實(shí)體,這意味著你可以在實(shí)體上指定一個(gè)約束,例如,實(shí)體中字段的最大長度為15,當(dāng)實(shí)體持久化,更新或移除時(shí)自動(dòng)驗(yàn)證字段,你可以在persistence.xml配置文件中使用<validation-mode>元素指定自動(dòng)驗(yàn)證生效的周期。
【51CTO.com譯稿,非經(jīng)授權(quán)請勿轉(zhuǎn)載。合作站點(diǎn)轉(zhuǎn)載請注明原文譯者和出處為51CTO.com,且不得修改原文內(nèi)容。】
原文:A More Complete Java Persistence API 作者:Ed Ort