Table 策略 (Table strategy)
這種策略中,持久化引擎 (persistence engine) 使用關系型數據庫中的一個表 (Table) 來生成主鍵。這種策略可移植性比較好,因為所有的關系型數據庫都支持這種策略。不同的 J2EE 應用服務器使用 不同的持久化引擎。
下面用一個例子來說明這種表生成策略的使用:
清單 1.Table 生成策略
@Entity public class PrimaryKey_Table { @TableGenerator(name = "PK_SEQ", table = "SEQUENCE_TABLE", pkColumnName = "SEQUENCE_NAME", valueColumnName = "SEQUENCE_COUNT") @Id @GeneratedValue(strategy =GenerationType.TABLE,generator="PK_SEQ") private Long id; //Getters and Setters //為了方便,類裡面除了一個必需的主鍵列,沒有任何其他列,以後類似 }
首先,清單 1 中使用 @javax.persistence.TableGenerator 這個注解來指定一個用來生成主鍵的表 (Table) ,這個注解可以使用在實體類上,也可以像這個例子一樣使用在主鍵字段上。
其中,在這個例子中,name 屬性 “PK_SEQ” 標示了這個生成器,也就是說這個生成器的名字是 PK_SEQ。這個 Table 屬性標示了用哪個表來存貯生成的主 鍵,在這個例子中,用“ SEQUENCE_TABLE” 來存儲主鍵,數據庫中有對應的 SEQUENCE_TABLE 表。其中 pkColumnName 屬 性用來指定的是生成器那個表中的主鍵,也就是 SEQUENCE_TABLE 這個表的主鍵的名字。屬性 valueColumnName 指定列是 用來存儲最後生成的那個主鍵的值。
也可以使用持久化引擎提供的缺省得 Table,例如:
清單 2. 使用確省 的表生成器
public class PK implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.TABLE) private Long id; // Getters and Setters }
不同的持久化引擎有不同的缺省值,在 glass fish 中,Table 屬性的缺省值是 SEQUENCE, pkColumnName 屬性 缺省值是 SEQ_NAME,,valueColumnName 屬性的缺省值是 SEQ_COUNT
Sequence 策略
一些數據庫,比如 Oralce,有一種內置的叫做“序列” (sequence)的機制來生成主鍵。為了調用這個序列,需要使用 @javax.persistence.SequenceGenerator 這個注解。
例如
清單 3.sequence 策略生成主鍵
public class PK_Sequence implements Serializable { private static final long serialVersionUID = 1L; @SequenceGenerator(name="PK_SEQ_TBL",sequenceName="PK_SEQ_NAME") @Id @GeneratedValue(strategy = GenerationType.SEQUENCE,generator="PK_SEQ_TBL") private Long id; // Getters and Setters }
其中的 @javax.persistence.SequenceGenerator 定義如下:
清單 4.@SequenceGenerator 注解的定義
@Target(value = {ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) @Retention(value = RetentionPolicy.RUNTIME) public @interface SequenceGenerator { public String name(); public String sequenceName() default ""; public String catalog() default ""; public String schema() default ""; public int initialValue() default 1; public int allocationSize() default 50; }
從定義中可以看出這個注解可以用在類上,也可以用在方法和字段上,其中 name 屬性指定的是所使用的生成器 ;sequenceName 指定的是數據庫中的序列;initialValue 指定的是序列的初始值,和 @TableGenerator 不同是它的缺省 值 1;allocationSize 指定的是持久化引擎 (persistence engine) 從序列 (sequence) 中讀取值時的緩存大小,它的缺 省值是 50。
Identity 策略
一些數據庫,用一個 Identity 列來生成主鍵,使用這個策略生成主鍵的時候, 只需要在 @GeneratedValue 中用 strategy 屬性指定即可。如下所示:
清單 5.strategy 策略生成主鍵
@Entity public class PK_Identity implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // Getters and Setters }
Auto 策略
使用 AUTO 策略就是將主鍵生成的策略交給持久化引擎 (persistence engine) 來決定,由它 自己選擇從 Table 策略,Sequence 策略和 Identity 策略三種策略中選擇合適的主鍵生成策略。不同的持久化引擎 (persistence engine) 使用不同的策略,在 galss fish 中使用的是 Table 策略。
使用 AUTO 策略時,我們可以 顯示使用,如:
清單 6.Auto 策略生成主鍵
@Entity public class PK_Auto implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; // Getters and Setters }
或則只使用:
@Generated Value
或者干脆什麼都不寫,因為缺省得主鍵生成策略就是 AUTO。
復合主鍵
在對象關系映射模型中,使用單獨的一個字段作為主鍵是一種非常好的做法,但是在實際應用中, 經常會遇到復合主鍵的問題,就是使用兩個或兩個以上的字段作為主鍵。比如,在一些歷史遺留的數據庫表中,經常出現復 合主鍵的問題,為了解決這種問題,JPA2.0 中采用的 @EmbeddedId 和 @IdClass 兩種方法解決這種問題。它們都需要將用 於主鍵的字段單獨放在一個主鍵類 (primary key class) 裡面,並且該主鍵類必須重寫 equals () 和 hashCode () 方法 ,必須實現 Serializable 接口,必須擁有無參構造函數。
@EmbeddedId 復合主鍵
清單 7 中的 NewsId 類 被用做主鍵類,它用 @Embeddable 注解進行了注釋,說明這個類可以嵌入到其他類中。之外這個類中還重寫了 hashCode () 和 equals () 方法, 因為這個類中的兩個屬性要用作主鍵,必須有一種判定它們是否相等並且唯一的途徑。
清 單 7.@EmbeddedId 中的主鍵類
@Embeddable public class NewsId implements Serializable { private static final long serialVersionUID = 1L; private String title; private String language; public String getLanguage() { return language; } public void setLanguage(String language) { this.language = language; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final NewsId other = (NewsId) obj; if ((this.title == null) ? (other.title != null) : !this.title.equals(other.title)) { return false; } if ((this.language == null) ? (other.language != null) : !this.language.equals( other.language)) { return false; } return true; } @Override public int hashCode() { int hash = 5; hash = 41 * hash + (this.title != null ? this.title.hashCode() : 0); hash = 41 * hash + (this.language != null ? this.language.hashCode() : 0); return hash; } }
清單 8 中的 News 類使用了清單 7 中定義的主鍵類,可以看倒非常簡單,只需要使用 @EmbeddedId 指定主鍵 類即可。
清單 8.News 實體類使用定義好的主鍵類
@Entity public class News implements Serializable { private static final long serialVersionUID = 1L; @EmbeddedId private NewsId id; private String content; // Getters and Setters }
清單 9 是持久化後生成的數據庫表的結構,可以看出來這個表如我們預想的一樣是 Title 和 Language 的聯合 主鍵。
清單 9. 使用主鍵類生成的表結構
CREATE TABLE `news` ( `CONTENT` varchar(255) default NULL, `TITLE` varchar(255) NOT NULL, `LANGUAGE` varchar(255) NOT NULL, PRIMARY KEY (`TITLE`,`LANGUAGE`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
IdClass 復合主鍵
IdClass 這種復合主鍵策略,在主鍵類上 和 Embeddable 這種復合主鍵策略稍有不同。如清單 10,這個策略中的主鍵類不需要使用任何注解 (annotation),但是仍 然必須重寫 hashCode() 和 equals() 兩個方法。其實也就是將 Embeddable 這種復合主鍵策略中的主鍵類的 @Embeddable 注解去掉就可以了。
清單 10. IdClass 復合主鍵策略中的主鍵類
public class NewsId implements Serializable { private static final long serialVersionUID = 1L; private String title; private String language; public String getLanguage() { return language; } public void setLanguage(String language) { this.language = language; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final NewsId other = (NewsId) obj; if ((this.title == null) ? (other.title != null) : !this.title.equals(other.title)) { return false; } if ((this.language == null) ? (other.language != null) : !this.language.equals( other.language)) { return false; } return true; } @Override public int hashCode() { int hash = 5; hash = 41 * hash + (this.title != null ? this.title.hashCode() : 0); hash = 41 * hash + (this.language != null ? this.language.hashCode() : 0); return hash; } }
從清單 11 中可以看出這個 News 實體類首先使用 @IdClass (NewsId.class) 注解指定了主鍵類。同時在類中 也復寫了主鍵類中的兩個字段,並分別用 @Id 進行了注解。
清單 11. IdClass 策略中使用復合主鍵的 News 類
@Entity @IdClass(NewsId.class) public class News implements Serializable { private static final long serialVersionUID = 1L; @Id private String title; @Id private String language; private String content; // Getters and Setters }
從清單 12 中可以看出,兩種復合主鍵的映射策略,持久化後映射到數據庫中的表的結構是相同的,一個明顯的 區別就是在查詢的時候稍有不同。如在使用 @EmbeddableId 策略的時候,要使用如下查詢語句:
Select n.newsId.title from news n
而使用 @IdClass 策略的時候,要使用如下查詢語句:
Select n.title from news n
另外一點就是使用 @IdClass 這種策略的時候,在復寫主鍵類中的字段的時候務必要保證和主鍵類中 的定義完全一樣。
清單 12. @IdClass 策略生成的表結構
CREATE TABLE `news` ( `CONTENT` varchar(255) default NULL, `TITLE` varchar(255) NOT NULL, `LANGUAGE` varchar(255) NOT NULL, PRIMARY KEY (`TITLE`,`LANGUAGE`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
總結
Java EE 項目開發中的持久層,雖然具體的實現方式, 也就是持久化引擎會隨著你選擇的 Java EE 服務器的不同而有所不同,但是在 JPA(java persistence API) 這個規范之下 ,每個實體的主鍵生成策略卻只有上面幾種,也就是說我們主要掌握了上面幾種主鍵生成策略,就可以在以後 Java EE 項 目持久層開發中以不變應萬變的姿態來面對紛繁復雜的具體情況了。