JPA 和 OpenJPA 簡介
隨著面向對象的數據模型被廣泛應用,將面向對象的實體映射為關系數據庫表項(OR Mapping)已經越來越多的出現在各類應用程序的開發設計之中。JPA(Java Persistence API)是 Sun Microssystems 公司制定的一個把 Java 數據對象映射成關系數據庫對象的一 個標准。JPA 彌補了 JDBC、ORM、EJB2 等在 Java 對象持久化方面的不足之處,並且非常易 於使用。JPA 因大量使用了像 annotations 和 generics 等 Java 語言的新特性,需要 J2SE 1.5(也被稱作 Java 5)或者更高版本的支持。Apache 的 OpenJPA 則是當前諸多熱門 Java 持久化實現之一。Apache OpenJPA 是 Apache 軟件基金下面的一個 Java 持久化技術 ,它可以被當作一個單獨的 POJO(Plain Old Java Object)持續化使用,或者被集成到任 意 Java EE 兼容的容器或其它的輕量級框架(例如 Tomcat、Spring)等一起使用。
標准 JPA annotation 及其在 OpenJPA 中的應用
標准 JPA 提供了不少 annotation,在 OpenJPA 中這些標准的 annotation 的用法如下 :
@entity
@entity 用來聲明一個類為一個持久化的實體類。該 annotation 只有唯一可選的屬性, 即 name 用於指定實體的名稱。
@table
@table 描述了一個實體類對應的數據庫表的信息。@table 有以下屬性:
String name: 表的名稱。
String schema: 該表對應的 schema,如果沒有指定 schema 的名稱,JPA 在數據庫連接 時使用默認的 schema。
String catalog: 表的目錄。如果沒有指定目錄,JPA 在數據庫連接時使用默認值。
UniqueContraint[] uniqueContraints: 應用於表的唯一性限制條件。Uniquecontraint 本身是個 annotation, 用於保證數據庫表的某一列(簡單數據類型或者復雜數據類型)在每 一個記錄中都是唯一的。
@column
@column 定義了數據庫表項的每一列的屬性,具體內容包括:
String name: 指定列的名稱,默認值為類的 field 名稱。
String columnDefinition: 數據庫系統特定的列類型名稱。在 OpenJPA 中,該屬性只適 用於數據庫系統通過那些能夠從元數據直接創建表的數據庫系統。在表的創建過程中,如果 該屬性值非空,那麼數據庫系統就以該屬性指定的類型來定義指定的列。
int length:指定列的長度。
int precision:小數列的精度。
int scale:定義小數類型的列最多可以容納的數字數目
boolean nullable: 該列是否能夠接受非空的記錄。
boolean insertable:定義該列是否可以通過 SQL INSERT 語句中指定生成,默認值為 true。
boolean updatable:定義該列是否可以通過 SQL UPDATE 語句中生成,默認值為 true。
String table:有的時候列並非定義在主表下,table 屬性就提供了將列映射到次表下的 方法。
@ Inheritance
在面向對象的類之間存在繼承關系,@inheritance 的出現就將繼承關系能夠映射到關系 數據庫模型中。在實體基類中定義繼承策略
InheritanceType stragegy:用以聲明繼承策略的枚舉類型,可取的值包括 SINGLE_TABLE,JOINED 和 TABLE_PER_CLASS:
InheritanceType.SINGLE_TABLE:把所有的子類及其父類都映射到同一張表中。
InheritanceType.JOINED: 類圖中每個類都映射到各自的表項,但是每張表中僅僅包含 在各個層次的類中定義的列。
Inheritance.TABLE_PER_CLASS:類圖中每個類都映射到各自的表現,和 JOINED 不同之 處在於每張表包含了對應的實體類所有的列。
清單 1. JPA annotation 使用范例
@Entity(name="OperatingSystem")
@Table(name="OperatingSystem")
@Category(value="System")
@Inheritance(strategy=InheritanceType.JOINED)
public class OperatingSystem extends System{
…
/**
* A string describing the operating system version number.
*/
@Column(name="OSVersion",length=64)
@Basic
private String OSVersion;
…
}
擴展的 annotation 及其使用
在實際應用中,JPA 已有的 annotation 往往不能滿足 OR 映射的所有需求。以下介紹通 過自定義 Annotation 來擴充 OpenJPA 功能的方法,從而可以更好滿足實際應用的需求。
@AbstractWithTable
在 OpenJPA 中,針對實體之間的繼承關系如何在數據庫中展現,共有三種方式:
SINGLE_TABLE,TABLE_PER_CLASS 和 JOINED。
當應用程序使用 TABLE_PER_CLASS 這種方式時,Java 的抽象類(Abstract Class)在 OpenJPA 中不會被映射到數據庫中的表項,同時抽象性阻止了用戶不能通過 OpenJPA 直接對 該類進行持久化的操作。有些應用需要保持一個類在持久化操作方面的抽象性,但是又能保 證該類在數據庫中有對應的表項。那麼該類必須被定義為 Java 的具體類,同時引入 @AbstractWithTable(如清單 2):
清單 2. @AbstractWithTable 的定義
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface AbstractWithTable {
}
@AbstractWithTable 的使用方式如清單 3 所示:
清單 3. 應用 @AbstractWithTable 定義實體類
@AbstractWithTable
@Entity
@EntityListeners(value = { TestAWTListener.class })
public class TestAWT {
@Basic
public String p;
}
從清單 3 中可見,除了 @Entity 之外,@AbstractWithTable 和 @EntityListeners 也 被應用到類 TestAWT 中。其中 @AbstarctWithTable 就是用來指定這個抽象類需要一個數據 庫表,同時類 TestAWT 並沒有使用 abstract 這個 Java 關鍵字。
OpenJPA 提供了 @EntityListeners 指定進行持久化操作前後的回調函數。定義 TestAWT 的回調函數如下清單 4 所示。
清單 4. 處理 @AbstractWithTable 的回調函數
public class TestAWTListener {
@PrePersist
public void prePersist(Object obj) throws Exception{
AbstractWithTable awt = obj.getClass().getAnnotation (AbstractWithTable.class);
if (awt != null) {
System.out.println("abstract class cannot be persisted");
throw new Exception();
}
}
}
在回調函數中檢測相關對象是否設置了 @AbstractWithTable,如果 @AbstractWithTable 被應用到某個類,異常將被拋出來從而阻止將此對象存入數據庫中。這樣通過 @AbstractWithTable 和回調函數就達到了控制類的 JPA 的持久化操作權限和該類能映射到 數據庫的表項的目的。
@MaximumValue 和 @MinimumValue
數據庫中最常見的情況就是對某個字段的取值范圍定義約束條件(Constraint),比如學 生的考試成績取值范圍必須在 0 到 100 這個范圍內。但是 OpenJPA 沒有提供相應的 Annotation 來指定某個屬性的取值范圍。
為了解決這個問題,自定義的 Annotation:@Maximum 和 @Minimum 被引入,如下清單 5 和 6 所示。它們分別用來表示最大值和最小值。
清單 5. @MaximiumValue 和 @MinimiumValue 的定義
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MaximumValue {
String value();
}
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MinimumValue {
String value();
}
清單 6. 應用 @Maximium 和 @Minimium
@Entity
@EntityListeners(value = { TestMaxMinListener.class })
public class TestMaxMin {
@Basic
@MinimumValue("1")
@MaximumValue("100")
public Integer value;
}
@MaximiumValue 和 @MinimiumValue 被應用於 Java 類的域中。
與 3.1 類似,在這裡設置了回調函數所在的類 TestMaxMinListener,函數如清單 7 所 示:
清單 7. 處理 @ MaximiumValue 和 @MinimiumValue 的回調函數
public class TestMaxMinListener {
@PrePersist
public void prePersist(Object obj) throws Exception {
TestMaxMin tmm = (TestMaxMin)obj;
Field value = tmm.getClass().getDeclaredField("value");
MaximumValue max = value.getAnnotation(MaximumValue.class);
MinimumValue min = value.getAnnotation (MinimumValue.class);
if (tmm.value > max.value() || tmm.value < min.value ()){
System.out.println("The value property is invalid.");
throw new Exception();
}
}
}
這個 TestMaxMinListener 的作用就是檢測相應的屬性值是否在規定的范圍內,如果超出 了規定的范圍,那麼就會拋出異常,從而防止將錯誤的數據存入數據庫中。
@Unsigned
與上面的情況相同,對於某些屬性的值,我們希望它是一個無符號整形數。而在 OpenJPA 中也沒有相關的 Annotation 來指定。可以自定義一個 Annottation:@Unsigned 用來表示 某個屬性的值是無符號數。
清單 8. @Unsigned 的定義
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Unsigned {
}
在 Entity Class 當中可以直接把 @Unsigned 設置在相應的屬性前面 , 如清單 9 所示 :
清單 9. 應用 @Unsigned
@Unsigned
@Basic
private Integer value;
@Unsigned 的處理方式和 @MaximiumnValue 類似,這裡就不再一一贅述。
@Required
數據庫對表中的各項字段可以定義限制條件,如某些字段不能為空,某些字段為主鍵等。 OpenJPA 定義的 @Column 中提供了字段以限制條件的映射,參考清單 11 中 @Column,將 nullable 設置為 false 來控制字段不能為空。
但是在圖 1 所示的應用中,OnlineGame.ServiceProvider 不能為 null,同時必須 SINGLE_TABLE 的繼承策略(參照表 1)來映射這一組類,如果僅僅應用 @Column 的 nullable 屬性,我們將不得不面臨這樣一個問題:當試圖持久化一個 PCGame 的實例到數據 庫中時,數據庫系統將會提示該數據不能插入,因為數據系統對插入表 1 中的數據要求 ServiceProvider 不能為空。
圖 1. 類圖
表 1 . 用 SINGLE_TABLE 在 DB 中存儲 Game,OnlineGame 和 PCGame
Game 的屬性 OnlineGame 的屬性 PCGame 的屬性 Name Version Type ServiceProvider … …
難道為了應用 SINGLE_TABLE 就不得不丟棄字段的約束條件麼? @Required 就應運而生 了,定義如清單 10。@Required 被用來標記 p1,p2 不能為 null。
就需要在我們自己的程序裡面檢測 @Required,並對相應的屬性值進行判斷。這一步通常 可以放在 EntityListener 中的 PrePersist 這個回調(Callback)函數中進行檢測。
清單 10. @Required 的定義
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Required {
}
在 OnlineGame Class 中,可以通過如下清單 11 所示方式設置屬性 ServiceProvider:
清單 11. 應用 @Required
@Column(name="",nullable=true)
@Basic
@Required
private String ServiceProvider;
數據庫不再對 ServiceProvider 是否為空做出約束,但是通過實現標記為 @prePresist 的回調函數,函數中檢測是否有 @Required,如果有 @Required,該字段就不能為空。如此 ServiceProvider 的約束條件就不會再成為約束類 PCGame 的條件。
@RelationshipMetaData
在 OpenJPA 中,有四個 Annotation 可以用來標識一個實體之間的關系,它們分別是: @OneToOne,@OneToMany,@ManyToOne 和 @ManyToMany。其中每一個 Annotation 都有以下 幾個屬性:targetEntity,mappedBy,cascade,fetch 和 optional。
圖 2. 實體 Employee 和 SoftwareLicense 的 ER 圖
盡管包括了一對一、一對多和多對多的情況,但是為了將圖 2 所示 ER 關系圖映射到關 系數據庫,以上的 Annotation 就不能滿足要求。圖 2 的關系 Owns 是一個多對多的關系, Owns 包括了兩個屬性 RequestDate 和 ExpiredDate,Owns 必須對應到一個實體類。類 Employee、SoftwareLicense 和 Owns 之間分別存在著多對一的關系,@ManytoOne 或者 @OnetoMany 雖然能夠標識出關系,但是並不能標識出關系的源或者目的是一個實體還是關系 ,@RelationshipMetaData 就有了用武之地,其定義如清單 12 所示:
清單 12. @RelationshipMetaData 的定義
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface RelationshipMetaData {
boolean isSource();
boolean isManyToManyWithProperties();
}
有了 @RelationshipMetaData,圖 2 對應的實體就可以被定義為:
清單 13. 應用 @RelationshipMetaData 實現圖 2 的 OR 映射
@Table(name="Employee")
@Entity(name="Employee")
public class Employee {
…
@RelationshipMetaData(isSource=true,isManyToManyWithProperties=true)
@OneToMany(mappedBy=”Source”)
private java.util.Collection<Employee_owns_SoftwareLicense> owns_SoftwareLicense;
…
};
@Table(name="SoftwareLicense")
@Entity(name="SoftwareLicense")
public class SoftwareLicense{
…
@RelationshipMetaData(isSource=false,isManyToManyWithProperties=true)
@OneToMany(mappedBy=”Target”)
private java.util.Collection<Employee_owns_SoftwareLicense> Employee_owns;
…
}
@Table(name=" Employee_owns_SoftwareLicense ")
@Entity(name=" Employee_owns_SoftwareLicense ")
public class Employee_owns_SoftwareLicense{
…
@ManyToOne
private Employee Source;
@ManyToOne
private SoftwareLicense Target;
…
}
總結
本文首先介紹了 JPA 和 Apache OpenJPA,以及它們在將面向對象的實體映射為關系數據 庫表項 (OR Mapping) 方面的應用。然後介紹了 JPA 提供的標准 Annotations, 並且舉例介 紹了其用途。但實踐表明已有的 annotation 常常不能滿足需求,結合筆者的經驗,一些擴 展的常用的 annotation 以更好的滿足 OR Mapping 的需求。通過提供自定義的 annotation ,不僅彌補了目前 OpenJPA 提供的標准 annotation 的功能缺陷,而且提升了用戶對 OR Mapping 過程的自適應調節和更靈活的控制,對於滿足有特殊 OR Mapping 需求的開發具有 非常實用的價值。