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