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 种策略肯定有自己的应用场景,也会有不同的追求者。本节课从客观上对三策略做了一个评估,选择谁由项目需求来决定。