簡單映射
近年來 ORM(Object-Relational Mapping,對象關系映射,即實體對象和數據庫表的映射)技術市場 熱鬧非凡,各種各樣的持久化框架應運而生,其中影響最大的是 Hibernate 和 Toplink。Sun 公司在充分吸收現有的優秀 ORM 尤其是 Hibernate 框架設計思想的基礎上,制定了新的 JPA(Java Persistence API)規范,對現在亂象叢生的持久 化市場帶來一個標准,大有統一持久化市場的氣勢。JPA 是通過 JDK5.0 注解或 XML 描述對象 - 關系表的映射關系,並將 運行期實體對象持久化到數據庫中去。JPA 規范小組的領導人就是 Hibernate 的發明者 Gavin King,JPA 規范的制定過程 中大量參考了 Hibernate 的內容,所以如果一個對 Hibernate 很熟悉的人,使用起來 JPA 會是輕車熟路,駕輕就熟的, 並且會感覺到更簡單一些,這主要得益於 JDK5 中引入的注解(annotation)。
下面就使用注解介紹一下 JPA 的使 用。
首先用個小例子介紹一下如何將一個單個 Java 類映射到數據庫中。
清單 1. Employee 實體
@Entity public class Employee implements Serializable { private static final long serialVersionUID = 1L; @Id private Long id; private String name; private int age; private String addree; // Getters and Setters }
如果沒有 @javax.persistence.Entity 和 @javax.persistence.Id 這兩個注解的話,它完全就是一個典型的 POJO 的 Java 類,現在加上這兩個注解之後,就可以作為一個實體類與數據庫中的表相對應。他在數據庫中的對應的表為 :
圖 1. Employee 表對應的 ER 圖
映射規則:
1. 實體類必須用 @javax.persistence.Entity 進行注解;
2. 必須使用 @javax.persistence.Id 來注解一個主鍵;
3. 實體 類必須擁有一個 public 或者 protected 的無參構造函數,之外實體類還可以擁有其他的構造函數;
4. 實體類必 須是一個頂級類(top-level class)。一個枚舉(enum)或者一個接口(interface)不能被注解為一個實體;
5. 實體類不能是 final 類型的,也不能有 final 類型的方法;
6. 如果實體類的一個實例需要用傳值的方式調用(例 如,遠程調用),則這個實體類必須實現(implements)java.io.Serializable 接口。
將一個 POJO 的 Java 類映 射成數據庫中的表如此簡單,這主要得益於 Java EE 5種引入的 Configuration by Exception 的理念,這個理念的核 心就是容器或者供應商提供一個缺省的規則,在這個規則下程序是可以正確運行的,如果開發人員有特殊的需求,需要改變 這個默認的規則,那麼就是對默認規則來說就是一個異常(Exception)。
如上例所示:默認的映射規則就是數據庫 表的名字和對應的 Java 類的名字相同,表中列的名字和 Java 類中相對應的字段的名字相同。
現在我們可以改變 這種默認的規則:
清單 2. 使用 @Table 和 @Column 注解修改映射規則
@Entity @Table(name="Workers") public class Employee implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue private Long id; @Column(name="emp_name", length=30) private String name; @Column(name="emp_age", nullable=false) private int age; @Column(name="emp_address", nullable=false ,unique=true) private String addree; // Getters and Setters }
改變默認規則後 在數據庫中對應的表為:
圖 2. 修改後的表對應的 ER 圖
首先我們可以可以使用
@ Javax.persistence.Table 這個注解來改變 Java 類在數據庫表種對應的表名。這個注解的定義如下:
清單 3. @Table 注解的定義
@Target(value = {ElementType.TYPE}) @Retention(value = RetentionPolicy.RUNTIME) public @interface Table { public String name() default ""; public String catalog() default ""; public String schema() default ""; public UniqueConstraint[] uniqueConstraints() default {}; }
從它的定義上可以看出來,這是一個類級別(class level)的注解 , 只能用在類的前面,其中 name 屬性的值 就是映射到數據庫中時對應表的名字,缺省是類名。
@javax.persistence.Column 注解,定義了列的屬性,你可以 用這個注解改變數據庫中表的列名(缺省情況下表對應的列名和類的字段名同名);指定列的長度;或者指定某列是否可以 為空,或者是否唯一,或者能否更新或插入。
它的定義如下:
清單 4. @Column 注解的定義
@Target(value = {ElementType.METHOD, ElementType.FIELD}) @Retention(value = RetentionPolicy.RUNTIME) public @interface Column { public String name() default ""; public boolean unique() default false; public boolean nullable() default true; public boolean insertable() default true; public boolean updatable() default true; public String columnDefinition() default ""; public String table() default ""; public int length() default 255; public int precision() default 0; public int scale() default 0; }
從它的定義可以看出他只可以用在類中的方法前面或者字段前面。
其中 name 屬性的值為數據庫中的列 名,unique 屬性說明該烈是否唯一,nullable 屬性說明是否可以為空,length 屬性指明了該列的最大長度等等。其中 table 屬性將在 @SecondaryTable 的使用中已有過介紹。
JPA 中兩種注解方式
JPA 中將一個類注解成實體 類(entity class)有兩種不同的注解方式:基於屬性(property-based)和基於字段(field-based)的注解。
1 ,基於字段的注解,就是直接將注解放置在實體類的字段的前面。前面的 Employee 實體類就是使用的這種注解方式;
2,基於屬性的注解,就是直接將注解放置在實體類相應的 getter 方法前面,而不是 setter 方法前面(這一點和 Spring 正好相反)。前面的 Employee 實體類如果使用基於屬性注解的方式就可以寫成如下形式。
清單 5. 基於屬 性的注解
@Entity @Table(name="Employees") public class Employee implements Serializable { private static final long serialVersionUID = 1L; private Long id; private String name; private int age; private String addree; @Id @GeneratedValue public Long getId() { return id; } public void setId(Long id) { this.id = id; } @Column(name="emp_address", nullable=false ,unique=true) public String getAddree() { return addree; } public void setAddree(String addree) { this.addree = addree; } @Column(name="emp_age", nullable=false) public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Column(name="emp_name", length=30) public String getName() { return name; } public void setName(String name) { this.name = name; } }
他在數據庫對應的表結構為:
圖 3. 基於屬性注解
可以看出,使用兩種注解方式在數據庫中映 射成的表都是相同的。
但是同一個實體類中必須並且只能使用其中一種注解方式,要麼是基於屬性的注解,要麼是 基於字段的注解。兩種不同的注解方式,在數據庫中對應的數據庫表是相同的,沒有任何區別,開發人員可以根據自己的喜 好任意選用其中一種注解方式。
@SecondaryTable 的使用
上面介紹的幾個例子都是一個實體類映射到數據庫 中的一個表中,那麼能否將一個實體類映射到數據庫兩張或更多表中呢表中呢。在有些情況下如數據庫中已經存在原始數據 類型,並且要求不能更改,這個時候如果能實現一個實體類對應兩張或多張表的話,將是很方便的。JPA2.0 中提供了一個 @SecondaryTablez 注解(annotation)就可以實現這種情況。下面用一個例子說明一下這個注解的使用方法:
清單 6. @SecondaryTable 的使用
@Entity @SecondaryTables({ @SecondaryTable(name = "Address"), @SecondaryTable(name = "Comments") }) public class Forum implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue private Long id; private String username; private String password; @Column(table = "Address", length = 100) private String street; @Column(table = "Address", nullable = false) private String city; @Column(table = "Address") private String conutry; @Column(table = "Comments") private String title; @Column(table = "Comments") private String Comments; @Column(table = "Comments") private Integer comments_length; // Getters and Setters }
清單 5 中定義了兩個 Secondary 表,分別為 Address 和 Comments,同時在 Forum 實體類中也通過 @Column 注解將某些子段分別分配給了這兩張表,那些 table 屬性得值是 Adress 的就會存在於 Address 表中,同理 table 屬性 的值是 Comments 的就會存在於 Comments 表中。那些沒有用 @Column 注解改變屬性默認的字段將會存在於 Forum 表中。 圖 4 就是持久化後在數據庫中對應的表的 ER 圖,從圖中可看出來,這些字段如我們預料的一樣被映射到了不同的表中。
圖 4. @SecondaryTable 持久化後對贏得 ER 圖
嵌套映射
在使用嵌套映射的時候首先要有一個被嵌套的類,清單 5 中 Address 實體類使用 @Embeddable 注解 ,說明這個就是一個可被嵌套的類,與 @EmbeddedId 復合主鍵策略中的主鍵類(primary key class)稍有不同的是,這個 被嵌套類不用重寫 hashCode() 和 equals() 方法,復合主鍵將在後面進行介紹。
清單 7. 被嵌套類
@Embeddable public class Address implements Serializable { private String street; private String city; private String province; private String country; // Getters and Setters }
清單 6 中 Employee 實體類是嵌套類的擁有者,其中使用了 @Embedded 注解將 Address 類嵌套進來了。
清單 8. 嵌套類的使用者
@Entity public class Employee implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; private String email; private String cellPhone; @Embedded private Address address; // Getters and Setters }
清單 7 是持久化後生成的數據庫表,可以看出被嵌套類的屬性,也被持久化到了數據庫中,默認的表名就是 嵌套類的擁有者的類名。
清單 9. 使用嵌套類生成的表結構
CREATE TABLE `employee` ( `ID` bigint(20) NOT NULL, `EMAIL` varchar(255) default NULL, `NAME` varchar(255) default NULL, `CELLPHONE` varchar(255) default NULL, `STREET` varchar(255) default NULL, `PROVINCE` varchar(255) default NULL, `CITY` varchar(255) default NULL, `COUNTRY` varchar(255) default NULL, PRIMARY KEY (`ID`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
被嵌套類的注解方式,field 方式或者 property 方式,依賴於嵌套 類的擁有者。上面例子中的 Employee 實體類采用的是 field 注解方式,那麼在持久化的過程中,被嵌套類 Address 也是 按照 field 注解方式就行映射的。
我們也可以通過 @Access 注解改變被嵌套類映射方式,清單 8 通過使用 @Access 注解將 Address 被嵌套類的注解方式設定成了 property 方式。清單 9 Employee 仍然采用 filed 注解方式。這 種情況下,持久化的時候,被嵌套類就會按照自己設定的注解方式映射,而不會再依賴於嵌套類的擁有者的注解方式。但這 並不會映射的結果。
清單 10. 基於 property 方式注解的被嵌套類
@Embeddable @Access(AccessType.PROPERTY) public class Address implements Serializable { private String street; private String city; private String province; private String country; @Column(nullable=false) public String getCity() { return city; } public void setCity(String city) { this.city = city; } @Column(nullable=false,length=50) public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } @Column(nullable=false,length=20) public String getProvince() { return province; } public void setProvince(String province) { this.province = province; } public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } }
清單 11. 基於 field 方式注解
@Entity @Access(AccessType. FIELD) public class Employee implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; private String email; private String cellPhone; @Embedded private Address address; // Getters and Setters }
事先設定被嵌套類的注解方式,是一種應該大力提倡的做法,因為當同一個類被不同的注解方式的類嵌套時, 可能會出現一些錯誤。
總結
簡單映射是 ORM,也就是對象關系映射中較為簡單的一種,他只是數據庫表與類 之間的一一對應,並未涉及表之間的關系,也就未涉及類與類之間的關系,也可以說是其他如繼承映射,關聯關系映射的基 礎,所以說熟悉並掌握簡單關系映射還是很有必要的。