Hibernate 中主鍵映射的助攻
1. 前言
本節課和大家一起聊聊 Hibernate 中的主鍵策略。通過本節課程,你將了解到:
- 什么是主鍵策略及主鍵生成器的種類;
- 如何映射復合主鍵。
2. 主鍵策略
Hibernate 進行數據庫操作時,可依靠主鍵生成器組件更快速、準確地進行一系列操作。這便是主鍵策略。
2.1 主鍵生成器
主鍵是關系數據庫中的概念,目的是唯一標識表中記錄,保證實體數據的完整性。
- 關系數據庫中表與表中數據的關系描述需依賴主鍵實現 ;
- 另有外鍵概念,所謂外鍵是在另一張表中對引用表的主鍵值的引用稱呼。
主外鍵關系指在不同的表中通過共同的字段信息建立起表中數據依賴(引用)關系。
回到 Hibernate 的世界!先展示一段代碼:
Student student = new Student(2, "Configuration老二", "男");
session.save(student);
上面的代碼功能:把應用程序中的數據寫入到數據庫中,沒毛病呀!
來!沒毛病找點毛病出來:
實際操作時,要求 Hibernate 把程序中 stuId 屬性的值插入到表中同名的 stuId 主鍵字段中。
主鍵有什么特點?
唯一性!回答得對。
請問在應用程序中構建數據時,如何確保賦值給 stuId 的值在表中不存在!這就是問題所在。
如何解決?
使用 Hibernate 主鍵生成器。
所謂主鍵生成器其作用就是在 Hibernate 向表中插入數據時,負責生成表中數據記錄的主鍵。
Hibernate 主鍵生成器 API 介紹:
- Hibernate 的主鍵生成器(generator)都實現了 org.hibernate.id.IdentityGenerator 接口;
public class IdentityGenerator extends AbstractPostInsertGenerator { …… }
- 開發者可以遵循這個接口規范提供自己的主鍵生成方案;
- Hibernate 內置有較多主鍵生成器,主鍵生成器都有自己的實現類,并提供有快捷名稱方便在注解或 XML 中引用。
常用主鍵生成器一覽:
-
org.hibernate.id.IncrementGenerator(increment):對 long、short 或 int 的數據列生成自動增長主鍵;
-
org.hibernate.id.IdentityGenerator(identity): 適用于 SQL server,MySql 等支持自動增長列的數據庫,適合 long、short 或 int 數據列類型;
-
org.hibernate.id.SequenceGenerator(sequecne):適用 oracle,DB2 等支持 Sequence 的數據庫,適合 long、short 或 int 數據列類型;
-
org.hibernate.id.UUIDGenerator(uuid):對字符串列的數據采用 128 - 位 uuid 算法生成唯一的字符串主鍵;
-
org.hibernate.id.Assigned(assigned):由應用程序指定,也是默認生成策略。
默認使用 assigned 生成器。這種方案要求開發者在應用程序中提供自己的主鍵生成算法:
- 調用保存方法之前,先帶著指定的值往數據庫中跑一趟,檢索是否存在重復,如果有,再試其它值;
- 調用保存方法之前,先檢索到表中 stuId 字段值的最大值,返回應用程序后遞增 1,用于 stuId 新值。如果多個用戶同時向數據中插入數據,這種方案會出問題,不適合并發操作環境。
使用 assigned 生成器除非有一個很完美的解決方案,否則建議只用于學習或測試環境。
本課程使用的是 Mysql 數據庫,最佳選擇 identity 生成器,主鍵值交給數據庫的自動增長列自動生成。
2.2 使用主鍵生成器重構代碼
- 在 Student 類的標識屬性(stuId)上標注如下注解;
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
public Integer getStuId() {
return stuId;
}
簡單得難以置信!空靈而干凈!!
使用 @GeneratedValue 注解確定主鍵生成器類型。GenerationType 是一個枚舉類型,有如下幾個選擇:
- AUTO:Hibernate 區分數據庫系統,自動選擇最佳策略;
- IDENTITY: 適合具有自動增長類型的數據庫,如 MySql……
- SEQUENCE: 適合如 Oracle 類型數據庫;
- TABLE: 使用 Hibernate 提供的 TableGenerator 生成器,不常用。
- 為了更好觀察生成的新數據,重建數據庫中的表。主配置文件中修改或添加如下配置信息;
<property name="hbm2ddl.auto">create</property>
- 執行插入數據實例;
// 打開事務
try{
transaction = session.beginTransaction();
// 添加一條學生信息,此處沒有指定學生編號
Student student = new Student("Hibernate 01", "男");
session.save(student);
transaction.commit();
} catch(Exception e) {
transaction.rollback();
} finally {
session.close();
}
-
進入 Mysql 系統查看,表結構中 stuId 自動設為主鍵,且為自動遞增;
-
查看表中數據,主鍵值自動生成;
-
試著多加幾條數據,別忘記修改如下配置信息。
<property name="hbm2ddl.auto">update</property>
大功告成??!
2.3 主鍵生成器
使用注解 @GeneratedValue 指定生成器類型后,Hibernate 一般情況下會自動創建對應的生成器對象,如前面指定類型為 IDENTITY,則創建生成 org.hibernate.id.IdentityGenerator 對象。
如果需要個性化定制生成器對象,則需要顯示指定生成器對象,如為 Oracle 數據庫指定主鍵生成器時,則配置可如下:
XML 映射方式:
<id name="stuId" type="Integer" column="stuId">
<generator class="sequence">
<param name="sequence">mySeq</param>
</generator>
</id>
注解映射方式:
@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE,generator="mySeqIdGen")
@SequenceGenerator(name="mySeqIdGen",sequenceName="mySeq")
public Integer getStuId() {
return stuId;
}
@SequenceGenerator 注解顯示指明使用 org.hibernate.id.SequenceGenerator 生成器對象,并指定使用數據庫中的命名為 mySeq 的序列化器。
其它主鍵生成器的使用本文不再復述,拋磚引玉,學習者可自行深入!
3. 復合主鍵
3.1 什么是復合主鍵
關系數據庫中,主鍵可指定一個字段實現,也可指定多個字段實現,這樣的主鍵叫復合主鍵。
從數據庫表設計原則分析,盡可能少用復合主鍵,但并不排除需要使用的場景。
對使用 Hibernate 的開發者而言,將面對一個新問題:在應用程序中,如何映射表中的復合主鍵?
應用程序中,復合主鍵映射方案有三:
- 將嵌入類注解為 @Embeddable,并將實體類的屬性注解為 @Id;
- 將實體類的屬性注解為 @EmbeddedId;
- 將實體類注解為 @IdClass,并將該實體類所有屬于主鍵的屬性都注解為 @Id
先分清楚兩個概念:
- 實體類:使用 @entity 注解的類;
- 嵌入類:使用 @Embeddable 注解的類;
3.2 復合主鍵映射方案一
實施流程
-
假設學生表中使用了 stuId,stuName 兩字段構成復合主鍵;
-
應用程序中構建兩個類;
嵌入類:
@Embeddable
public class StudentId {
private Integer stuId;
private String stuName;
public StudentId() {
super();
}
public StudentId(Integer stuId, String stuName) {
super();
this.stuId = stuId;
this.stuName = stuName;
}
//……省略get、set方法
嵌入類說明:
-
標注有 @Embeddable;
-
類中包括 stuId、stuName 兩個屬性與表中的復合字段相呼應;
-
必須實現 Serializable?。?!后續章節會聊到為什么。
實體類:
@Entity
public class Student_ {
private StudentId studentId;
private String stuSex;
public Student_() {
super();
}
public Student_(StudentId studentId, String stuSex) {
super();
this.studentId = studentId;
this.stuSex = stuSex;
}
@Id
public StudentId getStudentId() {
return studentId;
}
//……省略其它set、get方法
實體類說明:
實體類使用 @Entity 注解;
關鍵代碼分析:
關鍵點一: 內部添加引用嵌入類屬性。
private StudentId studentId;
關鍵點二: studentId 屬性上需要添加 @Id 注解。
@Id
public StudentId getStudentId() {
return studentId;
}
- 重新創建數據庫中的學生表:
<property name="hbm2ddl.auto">create</property>
-
運行測試實例。
Tips: 對于復合主鍵,需要在代碼級別指定值。
// 打開事務
transaction = session.beginTransaction();
// 添加一條學生信息
Student_ student = new Student_();
// 復合主鍵信息
StudentId studentId=new StudentId(1, "Hibernate是老大");
student.setStudentId(studentId);
student.setStuSex("男");
session.save(student);
transaction.commit();
- 查看 MySql,會發現新表 student_ 中指定復合主鍵,且數據添加成功。
如上所述,嵌入類就是復合主鍵映射類!
3.3 復合主鍵映射方案二
與第一方案相比,保留 @Entity 注解的實體類。
第一方案中的嵌入類上不再使用 @Embedded 注解,嵌入類降維成普通類。
實體類中不再使用 @Id 注解,而是使用 @EmbeddedId,此注解語義明確:一注解承擔兩注解任務。
可理解 @EmbeddedId 注解是 @Embedded 和 @Id 兩個注解的綜合體。
@EmbeddedId
public StudentId getStudentId() {
return studentId;
}
和第一方案一樣進行代碼測試,結果沒什么不一樣。
3.4 復合主鍵映射方案三
方案三與前兩個方案區別:
- 沒有嵌入類概念,前面的嵌入類降維成一個普通類,不加任何注解描述;
此類的作用僅僅在邏輯上把兩個標識屬性歸為一組!
public class StudentId implements Serializable{
private Integer stuId;
private String stuName;
public StudentId() {
super();
}
public StudentId(Integer stuId, String stuName) {
super();
this.stuId = stuId;
this.stuName = stuName;
}
//……省略set、get方法
- 實體類中使用 @IdClass 指定內部有標識屬性的類,另在實體類中也重復出現標識屬性且上面使用 @Id 注解。
@Entity
//指明實體類中標注有 @Id 的屬性為同一類型
@IdClass(StudentId.class)
public class Student_ {
private Integer stuId;
private String stuName;
private String stuSex;
public () {
super();
}
public Student_(Integer stuId, String stuName, String stuSex) {
super();
this.stuId = stuId;
this.stuName = stuName;
this.stuSex = stuSex;
}
@Id
public Integer getStuId() {
return stuId;
}
@Id
public String getStuName() {
return stuName;
}
//……省略set、get方法
}
測試代碼,結果和前面 2 個方案一樣。
3.5 方案比較
通過代碼的編寫過程,3 種方案優劣比較明顯:
- 第一種方案和第二種方案本質上沒有太多區別,只是一個使用 @Id 和 @Embeddable 兩個注解;一個是使用 @EmbeddedId 注解行使兩個注解的功能;
- 顯然,第二種方案稍優于第一方案,至少可少使用一個注解;
- 第三種方案代碼有重復之處,與 OOP 中的重用原則相違背,請慎用。
4. 小結
本節課,聊到了主鍵生成器,通過主鍵生成器這個助攻手,能有效地保持主鍵的唯一性,從而保證數據的完整性。
另聊了復合主鍵,復合主鍵映射備選方案雖多,但你可只記你心中最鐘情的那個。