近年來 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)。java
下面就使用註解介紹一下 JPA 的使用。web
首先用個小例子介紹一下如何將一個單個 Java 類映射到數據庫中。數據庫
@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 類,如今加上這兩個註解以後,就能夠做爲一個實體類與數據庫中的表相對應。他在數據庫中的對應的表爲:app
映射規則:框架
1. 實體類必須用 @javax.persistence.Entity 進行註解;函數
2. 必須使用 @javax.persistence.Id 來註解一個主鍵;this
3. 實體類必須擁有一個 public 或者 protected 的無參構造函數,以外實體類還能夠擁有其餘的構造函數;spa
4. 實體類必須是一個頂級類(top-level class)。一個枚舉(enum)或者一個接口(interface)不能被註解爲一個實體;.net
5. 實體類不能是 final 類型的,也不能有 final 類型的方法;設計
6. 若是實體類的一個實例須要用傳值的方式調用(例如,遠程調用),則這個實體類必須實現(implements)java.io.Serializable 接口。
將一個 POJO 的 Java 類映射成數據庫中的表如此簡單,這主要得益於 Java EE 5種引入的 Configuration by Exception 的理念,這個理念的核心就是容器或者供應商提供一個缺省的規則,在這個規則下程序是能夠正確運行的,若是開發人員有特殊的需求,須要改變這個默認的規則,那麼就是對默認規則來講就是一個異常(Exception)。
如上例所示:默認的映射規則就是數據庫表的名字和對應的 Java 類的名字相同,表中列的名字和 Java 類中相對應的字段的名字相同。
如今咱們能夠改變這種默認的規則:
@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 }
改變默認規則後 在數據庫中對應的表爲:
首先咱們能夠能夠使用
@ Javax.persistence.Table 這個註解來改變 Java 類在數據庫表種對應的表名。這個註解的定義以下:
@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 註解,定義了列的屬性,你能夠用這個註解改變數據庫中表的列名(缺省狀況下表對應的列名和類的字段名同名);指定列的長度;或者指定某列是否能夠爲空,或者是否惟一,或者可否更新或插入。
它的定義以下:
@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 中將一個類註解成實體類(entity class)有兩種不一樣的註解方式:基於屬性(property-based)和基於字段(field-based)的註解。
1,基於字段的註解,就是直接將註解放置在實體類的字段的前面。前面的 Employee 實體類就是使用的這種註解方式;
2,基於屬性的註解,就是直接將註解放置在實體類相應的 getter 方法前面,而不是 setter 方法前面(這一點和 Spring 正好相反)。前面的 Employee 實體類若是使用基於屬性註解的方式就能夠寫成以下形式。
@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; } }
他在數據庫對應的表結構爲:
能夠看出,使用兩種註解方式在數據庫中映射成的表都是相同的。
可是同一個實體類中必須而且只能使用其中一種註解方式,要麼是基於屬性的註解,要麼是基於字段的註解。兩種不一樣的註解方式,在數據庫中對應的數據庫表是相同的,沒有任何區別,開發人員能夠根據本身的喜愛任意選用其中一種註解方式。
上面介紹的幾個例子都是一個實體類映射到數據庫中的一個表中,那麼可否將一個實體類映射到數據庫兩張或更多表中呢表中呢。在有些狀況下如數據庫中已經存在原始數據類型,而且要求不能更改,這個時候若是能實現一個實體類對應兩張或多張表的話,將是很方便的。JPA2.0 中提供了一個 @SecondaryTablez 註解(annotation)就能夠實現這種狀況。下面用一個例子說明一下這個註解的使用方法:
@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 圖,從圖中可看出來,這些字段如咱們預料的同樣被映射到了不一樣的表中。
在使用嵌套映射的時候首先要有一個被嵌套的類,清單 5 中 Address 實體類使用 @Embeddable 註解,說明這個就是一個可被嵌套的類,與 @EmbeddedId 複合主鍵策略中的主鍵類(primary key class)稍有不一樣的是,這個被嵌套類不用重寫 hashCode() 和 equals() 方法,複合主鍵將在後面進行介紹。
@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 類嵌套進來了。
@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 是持久化後生成的數據庫表,能夠看出被嵌套類的屬性,也被持久化到了數據庫中,默認的表名就是嵌套類的擁有者的類名。
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 註解方式。這種狀況下,持久化的時候,被嵌套類就會按照本身設定的註解方式映射,而不會再依賴於嵌套類的擁有者的註解方式。但這並不會映射的結果。
@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; } }
@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 }
事先設定被嵌套類的註解方式,是一種應該大力提倡的作法,由於當同一個類被不一樣的註解方式的類嵌套時,可能會出現一些錯誤。