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