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