JavaEE6引入的JPA2.0四大新特性詳解(轉)
Java EE 5平臺引入了Java持久化API(Java Persistence API,JPA),它為Java EE和Java SE應用程式提供了一個基於POJO的持久化模組。JPA處理關係資料與Java物件之間的對映,它使物件/關係(O/R)對映標準化,JPA已經被廣泛採用,已經成為事實上的O/R持久化企業標準。
Java EE 6帶來了JPA的最新版本 — JSR 317:Java持久化2.0,JPA 2.0帶來了許多新特性和增強,包括
1、物件/關係對映增強;
2、Java持久化查詢語言增強;
3、一種新的基於標準的查詢API;
4、支援悲觀鎖定。
物件/關係對映增強
JPA 1.0支援集合的對映,但這些集合只能包含實體,JPA 2.0增加了集合對映的基礎資料型別,如String和Integer,以及嵌入式物件的集合。JPA中的嵌入式物件是一個不能存在於它自身的物件,而是作為父物件的一部分存在,即它的資料不是存在於它自己的表中,而是嵌入在父物件的表中。
JPA 2.0增加了兩個支援新的集合對映的註解:@ElementCollection 和 @CollectionTable。使用@ElementCollection註解指定集合的嵌入式物件,這些集合是獨立儲存在集合表中的,使用@CollectionTable註解指定集合表的詳細資訊,如它包含的列。
下面是一個嵌入式類,表示了車輛的訪問服務,它儲存了訪問的日期,描述和費用,此外,車輛可以配備一或多個可選功能,每個功能是FeatureType型別的一個列舉值。
- public enum FeatureType { AC, CRUISE, PWR, BLUETOOTH, TV, ... }
- @Embeddable
- public class ServiceVisit {
- @Temporal(DATE)
- @Column(name="SVC_DATE")
- Date serviceDate;
- String workDesc;
- int cost;
- }
列舉值和嵌入式物件可以在一個表示車輛服務歷史的實體中使用,如
- @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;
- ...
- }
Vehicle實體中的第一對註解@ElementCollection 和 @CollectionTable指定FeatureType值儲存在VEH_OPTNS集合表中,第二對註解@ElementCollection 和 @CollectionTable指定ServiceVisit嵌入式物件儲存在VEH_SVC集合表中。
雖然在例子中沒有顯示,@ElementCollection註解有兩個屬性:targetClass 和 fetch。targetClass屬性指定基礎類或嵌入式類的類名,如果欄位或屬性是使用泛型定義的,那這兩個屬性是可選的,上面這個例子就是這樣。 Fetch屬性是可選的,它指定集合是延後檢索還是立即檢索,使用javax.persistence.FetchType常量,值分別用LAZY和 EAGER,預設情況下,集合是延後匹配的。
JPA 2.0中還有其它許多關於物件/關係對映的增強,例如,JPA 2.0支援巢狀式嵌入,關係嵌入和有序列表,也增加了新的註解增強對映功能,通過@Access註解更靈活地支援特定的訪問型別,更多用於實體關係的對映選項,如對單向一對多關係的外來鍵對映支援,通過@MapsId註解支援派生身份,支援孤體刪除。
Java持久化查詢語言增強
JPA 1.0定義了一個廣泛的Java持久化查詢語言,使用它你可以查詢實體和它們的持久化狀態。JPA 2.0對JPQL做了大量改進,如現在可以在查詢中使用case表示式。在下面的查詢中,如果僱員的評分為1,則通過乘以1.1對僱員的薪水進行了增長,如果評分為2,則乘以1.05,其它評分則乘以1.01。
UPDATE Employeee
Java程式碼- 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增加了大量新的運算子,如NULLIF和COALESCE,當資料庫使用其它非空資料解碼時,NULLIF運算子是非常有用的,使用 NULLIF,你可以在查詢中將這些值轉換為空值,如果引數等於NULLIF,NULLIF會返回空值,否則返回第一個引數的值。
假設薪水資料儲存在employee表中,資料型別為整數,卻掉的薪水解碼為-9999,下面的查詢返回薪水的平均值,為了正確地忽略卻掉的薪水,查詢使用NULLIF將-9999轉換為空值。SELECT AVG(NULLIF(e.salary, -99999)) FROM Employeee
COALESCE運算子接收一串引數,從列表中返回第一個非空值,相當於下面的case表示式
value1 IS NOT NULL THEN value1
Java程式碼- WHEN value2 IS NOT NULL THEN value2
- WHEN value3 IS NOT NULL THEN value3
- ...
- ELSE NULL END
COALESCE運算子接收一串引數,從列表中返回第一個非空值,相當於下面的case表示式
SELECT Name, COALESCE(e.work_phone, e.home_phone) phone FROM Employeee
假設employee表包括一個辦公電話號碼和家庭電話號碼列,無電話號碼的列使用空值表示。下面的查詢返回每個僱員的姓名和電話號碼,COALESCE運算子指定查詢返回辦公電話號碼,但如果為空,則返回家庭電話號碼,如果兩者都為空,則返回一個空值。
JPA 2.0向JPQL增加的其它運算子是INDEX,TYPE,KEY,VALUE和ENTRY。INDEX運算子指定查詢時的排序順序,TYPE運算子選擇一個實體的型別,將查詢限制到一或多個實體型別,KEY,VALUE和ENTRY運算子是JPA 2.0中的泛化對映功能的一部分。使用KEY運算子提取對映鍵,VALUE運算子提取對映值,ENTRY運算子選擇一個對映實體。 此外,JPA 2.0增加了選擇列表、以及集合值引數和非多型查詢中運算子的支援。 標準的API JPA 2.0中引入的另一個重要特性是標準的API,利用這個API可以動態地構建基於物件的查詢,本質上,這個標準API等價於面向物件的JPQL,使用它,你可以使用基於物件的方法建立查詢,不再需要JPQL使用的字串操作。 標準API是基於元模型的,元模型是一個提供了架構級關於持久化單元託管類的元資料的抽象模型, 元模型讓你構建強型別的查詢,它也允許你瀏覽持久化單元的邏輯結構。 通常,一個註解處理器使用元模型生成靜態元模型類,這些類提供持久化狀態和持久化單元託管類的關係,但你可以對靜態元模型類編碼。下面是一個實體例項 @Entity Java程式碼- public class Employee {
- @Id Long Id;
- String firstName;
- String lastName;
- Department dept;
- }
- 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 volatileSingularAttribute<Employee, String> firstName;
- public static volatile SingularAttribute<Employee, String> lastName;
- public static volatile SingularAttribute<Employee, Department> dept;
- }
此外,JPA 2.0元模型API允許你動態訪問元模型,因此當你使用標準API時,既可以靜態訪問元模型類,也可以動態訪問元模型類。標準API提供了更好的靈活性,既可以使用基於物件的方法,又可以使用基於字串的方法導航元模型,這意味著你有四種使用標準API的方法
1、靜態使用元模型類 2、靜態使用字串 3、動態使用元模型 4、動態使用字串 無論你使用哪種方法,通過構造一個CriteriaQuery物件定義一個基於標準API的查詢時,使用一個工廠物件 CriteriaBuilder構造CriteriaQuery,可以從EntityManager 或 EntityManagerFactory類中獲得CriteriaBuilder。下面的程式碼構造一個CriteriaQuery物件 EntityManager em = ... ; Java程式碼- CriteriaBuilder cb = em.getCriteriaBuilder();
- CriteriaQuery<Customer> cq = cb.createQuery(Customer.class);
注意CriteriaQuery物件是泛型型別,使用CriteriaBuilder 的createQuery方法建立一個CriteriaQuery,併為查詢結果指定型別。在這個例子中,createQuery 方法的Employee.class引數指定查詢結果型別是Employee。CriteriaQuery物件和建立它們的方法是強型別的。
接下來,為CriteriaQuery物件指定一個或多個查詢源,查詢源表示查詢基於的實體。你建立一個查詢源,然後使用 AbstractQuery介面的from()方法將其新增到查詢。AbstractQuery介面是眾多介面中的一員,如 CriteriaQuery,From和root,它們都定義在標準API中。CriteriaQuery介面繼承AbstractQuery介面的屬性。 from()方法的引數是實體類或EntityType實體的例項,from()方法的結果是一個Root物件,Root介面擴充套件From介面,它表示某個查詢的from子句中可能出現的物件。 下面的程式碼增加一個查詢源到CriteriaQuery物件 CriteriaBuilder cb = em.getCriteriaBuilder(); Java程式碼- CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
- Root<Employee> emp = cq.from(Employee.class);
當你向CriteriaQuery物件新增一個或多個查詢源後,你訪問元模型,然後構造一個查詢表示式,你如何做取決於你是以靜態方式提交查詢還是以動態方式提交查詢,以及是使用元模型還是字串導航元模型。下面是一個使用元模型類靜態查詢的例子
cq.select(emp);
Java程式碼- cq.where(cb.equal(emp.get(Employee_.lastName), "Smith"));
- TypedQuery<Employee> query = em.createQuery(cq);
- List<Employee> rows = query.getResultList();
CriteriaQuery介面的select() 和 where()方法指定查詢結果返回的選擇專案。
注意,你使用EntityManager建立查詢時,可以在輸入中指定一個CriteriaQuery物件,它返回一個TypedQuery,它是JPA 2.0引入javax.persistence.Query介面的一個擴充套件,TypedQuery介面知道它返回的型別。 在元模型術語中,Employee_是對應於Employee實體類的規範化元模型類,一個規範化元模型類遵循JPA 2.0規範中描述的某些規則。例如,元模型類的名字於託管類,一般都是在託管類名字後面追加一個下劃線“_”。一個規範化元模型是一個包含靜態元模型類的元模型,這個靜態元模型對應於實體,對映的超類,以及持久化單元中的嵌入式類。實際上,這個查詢使用了規範化元模型。下面是一個完整的查詢 EntityManager em = ... ; Java程式碼- 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查詢的動態版本:
EntityManager em = ... ;
Java程式碼- 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();
使用元模型API的標準查詢提供了與使用規範化元模型相同的型別,但它比基於規範化元模型的查詢更冗長。
Root的getModel()方法返回根對應的元模型實體,它也允許執行時訪問在Employee實體中宣告的持久化屬性。
getSingularAttribute()方法是一個元模型API方法,它返回一個持久化的單值屬性或欄位,在這個例子中,它返回值為Smith 的lastName屬性。下面是使用字串的元資料導航查詢的靜態版本:
EntityManager em = ... ;
Java程式碼- 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();
這個基於字串的方法要相對容易使用些,但卻失去了元模型具有的型別安全。
支援悲觀鎖
鎖是處理資料庫事務併發的一種技術,當兩個或更多資料庫事務併發地訪問相同資料時,鎖可以保證同一時間只有一個事務可以修改資料。
鎖的方法通常有兩種:樂觀鎖和悲觀鎖。樂觀鎖認為多個併發事務之間很少出現衝突,也就是說不會經常出現同一時間讀取或修改相同資料,在樂觀鎖中,其目標是讓併發事務自由地同時得到處理,而不是發現或預防衝突。兩個事務在同一時刻可以訪問相同的資料,但為了預防衝突,需要對資料執行一次檢查,檢查自上次讀取資料以來發生的任何變化。
悲觀鎖認為事務會經常發生衝突,在悲觀鎖中,讀取資料的事務會鎖定資料,在前面的事務提交之前,其它事務都不能修改資料。
JPA 1.0只支援樂觀鎖,你可以使用EntityManager類的lock()方法指定鎖模式的值,可以是READ或WRITE,如
EntityManager em = ... ;
Java程式碼- em.lock (p1, READ);
對於READ鎖模式,JPA實體管理器在事務提交前都會鎖定實體,檢查實體的版本屬性確定實體自上次被讀取以來是否有更新,如果版本屬性被更新了,實體管理器會丟擲一個OptimisticLockException異常,並回滾事務。
對於WRITE鎖模式,實體管理器執行和READ鎖模式相同的樂觀鎖操作,但它也會更新實體的版本列。
JPA 2.0增加了6種新的鎖模式,其中兩個是樂觀鎖。JPA 2.0也允許悲觀鎖,並增加了3種悲觀鎖,第6種鎖模式是無鎖。
下面是新增的兩個樂觀鎖模式:
1、OPTIMISTIC:它和READ鎖模式相同,JPA 2.0仍然支援READ鎖模式,但明確指出在新應用程式中推薦使用OPTIMISTIC。
2、OPTIMISTIC_FORCE_INCREMENT:它和WRITE鎖模式相同,JPA 2.0仍然支援WRITE鎖模式,但明確指出在新應用程式中推薦使用OPTIMISTIC_FORCE_INCREMENT。
下面是新增的三個悲觀鎖模式:
1、PESSIMISTIC_READ:只要事務讀實體,實體管理器就鎖定實體,直到事務完成鎖才會解開,當你想使用重複讀語義查詢資料時使用這種鎖模式,換句話說就是,當你想確保資料在連續讀期間不被修改,這種鎖模式不會阻礙其它事務讀取資料。
2、PESSIMISTIC_WRITE:只要事務更新實體,實體管理器就會鎖定實體,這種鎖模式強制嘗試修改實體資料的事務序列化,當多個併發更新事務出現更新失敗機率較高時使用這種鎖模式。
3、PESSIMISTIC_FORCE_INCREMENT:當事務讀實體時,實體管理器就鎖定實體,當事務結束時會增加實體的版本屬性,即使實體沒有修改。
你也可以指定新的鎖模式NONE,在這種情況下表示沒有鎖發生。
JPA 2.0也提供了多種方法為實體指定鎖模式,你可以使用EntityManager的lock() 和 find()方法指定鎖模式。此外,EntityManager.refresh()方法可以恢復實體例項的狀態。
下面的程式碼顯示了使用PESSIMISTIC_WRITE鎖模式的悲觀鎖
// read
Java程式碼- 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);
在這個例子中,它首先讀取一些資料,然後應用PESSIMISTIC_WRITE鎖,在更新資料之前呼叫 EntityManager.refresh()方法,當事務更新實體時,PESSIMISTIC_WRITE鎖鎖定實體,其它事務就不能更新相同的實體,直到前面的事務提交。
更多JPA 2.0的新特性 除了前面描述的增強和新特性外,JPA 2.0可以使用Bean驗證自動驗證實體,這意味著你可以在實體上指定一個約束,例如,實體中欄位的最大長度為15,當實體持久化,更新或移除時自動驗證欄位,你可以在persistence.xml配置檔案中使用元素指定自動驗證生效的週期。