Hibernate 繼承映射
1. 前言
本節課程和大家一起學習繼承映射。通過本節課程的學習,你將了解到:
- 什么是繼承映射;
- 實現繼承映射的 3 種方案。
2. 繼承映射
學習繼承映射之前,需要搞清楚什么是繼承映射?
繼承是 OOP 中的概念,其目的除了復用代碼之外,還用來描述對象在現實世界中的關系。
為了更好地講解繼承映射,咱們再在數據庫中創建一張老師表。數據庫中多了一張表,按照使用 Hibernate 的套路,理所當然應該在程序中添加一個老師類。
@Entity
public class Teacher {
private Integer teacherId;
private String teacherName;
private Integer serviceYear;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer getTeacherId() {
return teacherId;
}
//省略其它……
}
從 OOP 的角度進行分析,可以為學生類和老師類創建一個共同的父類,描述兩者共同的屬性。
所以,你會看到如下 3 個類型:
public class Person implements Serializable {
}
public class Teacher extends Person implements Serializable {
}
public class Student extends Person implements Serializable {
}
程序中通過 OOP 繼承語法重新描述了學生類和老師類的關系,程序中結構上的變化,必然會讓 Hibernate 茫然不知所措,因為關系型數據庫中是沒有繼承一說的。
此時,就需要告訴 Hibernate 如何把程序中的繼承關系映射到數據庫中。
這就叫做繼承映射!
3. 繼承映射的實現
知道了什么是繼承映射,現在就到了怎么實現的環節。
先介紹大家認識一下 @Inheritance 注解,識其名,知其意,繼承映射的實現就是靠它實現的。
并且它還提供了 3 種方案。
3 種方案各有自身的使用場景,如何選擇,根據實際情況定奪。
來!排好隊,開始點名。
3.1 SINGLE_TABLE 策略
SINGLE_TABLE 策略: 數據庫中使用一張表結構描述 OOP 中的繼承關系。
學生數據、老師數據以及其它工作人員的信息都放在一張表中??上攵?,這種映射的實用價值并不是很大,因為沒有較好地遵循數據庫設計范式。
留一個問題給大家思考:數據庫設計范式有哪些?
既然大家都擠在一張表里,一想想,就覺得悶得慌。天呀,都在一起,怎么區分這張表中的數據誰是誰?
添加一個鑒別器字段!
所謂鑒別器字段,就是在表中添加了一個字段區分彼此之間的身份,這個字段充當的就是鑒別器(discriminator)的功能。
表中的數據可能是這樣子:
不敢直視,有點像住混合宿舍,大通鋪的那種。
對于這種策略,建議用于數據關系不是很復雜的應用場景下。
貼上關鍵的注解映射代碼:
Peson 類:
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "discriminator", discriminatorType = DiscriminatorType.STRING)
@DiscriminatorValue("person")
public class Person implements Serializable {
//標識
private Integer id;
//姓名
private String name;
//性別
private String sex;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer getId() {
return id;
}
//省略其它……
}
細究一下上面用到的 2 個注解:
- @Inheritance: 繼承映射注解,此注解有一個很重要的 strategy 屬性,strategy 屬性是一個枚舉類型,有 3 個可選值,也就是 3 種繼承映射 策略:
InheritanceType.SINGLE_TABLE
InheritanceType.TABLE_PER_CLASS
InheritanceType.JOINED
- @DiscriminatorColumn:
@DiscriminatorColumn(name = "discriminator",discriminatorType=DiscriminatorType.STRING)
此注解的作用就是添加一個冗余的識別字段,用來區分表中彼此的身份。
@DiscriminatorValue("person")
對于 Persono 類的信息區分關鍵字是 person。你可以指定任意的你覺得有意思的名字。
學生類中只需要出現僅屬于自己的屬性,再標注自己的身份說明標簽:
@Entity
@DiscriminatorValue("student")
public class Student extends Person implements Serializable {
//最喜歡的課程
private String loveCourse;
//其它代碼……
}
老師類:
@Entity
@DiscriminatorValue("teacher")
public class Teacher extends Person{
//工作年限
private Integer serviceYear;
//其它代碼……
}
修改主配置文件中的信息:
<property name="hbm2ddl.auto">create</property>
<mapping class="com.mk.po.inheritance.Person" />
<mapping class="com.mk.po.inheritance.Student" />
<mapping class="com.mk.po.inheritance.Teacher" />
測試下面的實例,僅僅只是為了創建新表,不用添加任何具體的操作代碼。
HibernateTemplate<Student> hibernateTemplate = new HibernateTemplate<Student>();
hibernateTemplate.template(new Notify<Student>() {
@Override
public Student action(Session session) {
return null;
}
});
進入 MySql,查看表生成情況:
有且僅有一張表。
大功告成,這種映射策略不再細究,如果有興趣,添加、查詢數據等操作自己去玩。
3.2 TABLE_PER_CLASS 策略
TABLE_PER_CLASS: 每一個類對應一張表,每一張表中保存自己的數據。
最后的數據保存方式如下:
貼出 3 個類中的注解信息:
Person 類:
@Entity
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public class Person implements Serializable {
//標識
private Integer id;
//姓名
private String name;
//性別
private String sex;
@Id
public Integer getId() {
return id;
}
//其它代碼……
}
Person 類中不再需要 鑒別器。這里有一個坑要引起注意, id 屬性上不要添加主鍵生成器相關的注解。
Student 類:
@Entity
public class Student extends Person implements Serializable {
//最喜歡的課程
private String loveCourse;
//其它信息
}
Teacher 類:
@Entity
public class Teacher extends Person{
//工作年限
private Integer serviceYear;
//其它代碼……
}
執行測試實例,重新創建新表
HibernateTemplate<Student> hibernateTemplate = new HibernateTemplate<Student>();
hibernateTemplate.template(new Notify<Student>() {
@Override
public Student action(Session session) {
return null;
}
});
直接進入 MySql 查看一下表生成情況:
類、表結構都有了,該干嘛去干嘛。
當然,繼續下面內容之前,評價一下這種策略。這種方式應該是符合主流要求的,建議大家使用這種方式。
3.3 JOINED 策略
JOINED: 將父類、子類分別存放在不同的表中,并且建立相應的外鍵,以確定相互之間的關系。
將來的數據應該和下面一樣:
第三種策略的映射代碼和第二種策略唯一不同的地方,就在 person 中的策略改成了:
@Inheritance(strategy=InheritanceType.JOINED)
好吧,跑一下測試實例:
HibernateTemplate<Student> hibernateTemplate = new HibernateTemplate<Student>();
hibernateTemplate.template(new Notify<Student>() {
@Override
public Student action(Session session) {
return null;
}
});
進入 MySql 查看生成的表結構:
這種策略從表內容來講,會把學生和老師共同的字段信息保存到一張表中,兩個子表只分別保存屬于自己的信息。
JOINED 策略從使用角度上講增加了查詢時間,對學生、老師信息進行保存和查詢操作時需要連接 person 表,顯然增加了操作時間。
并且,表中的數據不完善,有點殘缺不全的感覺。
相信各自還是有自己的優缺點:
- SINGLE_TABLE: 除了速度杠桿的,但不分你我,數據擠在一起,只怕數據多了,遲早會出現異常;
- TABLE_PER_CLASS: 類結構符合 OOP 標準,表結構符合關系型數據庫范式。數據之間分界線清晰,操作速度也還可以;
- JOINED: 和 SINGLE_TABLE 有點類似,原來是全部擠在一起。為了緩解空間,一部分數據擠在一起,另一部分放在自己的表中,速度不會提升,數據表完整性得不到保存。
客觀上對 3 種策略進行縱橫比較,最后選擇使用哪一種策略,還是由項目需求決定吧。
存在,就有合理性。
4. 小結
本節課講解了繼承映射,學習了 3 種繼續映射的實現。
3 種策略肯定有自己的應用場景,也會有不同的追求者。本節課從客觀上對三策略做了一個評估,選擇誰由項目需求來決定。