在面向对象的程序领域中,类与类之间是有继承关系的,例如Java世界中只需要extends关键字就可以确定这两个类的父子关系,但是在关系数据库的世界 中,表与表之间没有任何关键字可以明确指明这两张表的父子关系,表与表是没有继承关系这样的说法的。为了将程序领域中的继承关系反映到数据 中,Hibernate为我们提供了3中方案:
第一种方案:一个子类对应一张表。
第二种方案:使用一张表表示所有继承体系下的类的属性的并集。
第三种方案:每个子类使用一张表只存储它特有的属性,然后与父类所对应的表以一对一主键关联的方式关联起来。
这三种方案用官方的语言来说就是:
TPS:每个子类一个表(table per subclass) 。
TPH:每棵类继承树使用一个表(table per class hierarchy)
TPC:类表继承。每个具体类一个表(table per concrete class)(有一些限制)
现在我们就根据一个实例来看一下这三种方案的各自优缺点,一起来熟悉一下这三种方案。现在假设有People、Student、Teacher三个类,父类为People,Student与Teacher为People的父类,代码如下:
People类:
- public class People
- {
- /*父类所拥有的属性*/
- private Stringid;
- private Stringname;
- private Stringsex;
- private Stringage;
- private Timestampbirthday;
- /*get和set方法*/
- }
Student类:
- public class Student extends People
- {
- /*学生独有的属性*/
- private String cardId;//学号
- public String getCardId()
- { return cardId;}
- public void setCardId(String cardId)
- {
- this.cardId = cardId;
- }}
Teacher类:
- public class Teacher extends People
- {
- /*Teacher所独有的属性*/
- privateint salary;//工资
- public int getSalary()
- {
- return salary;
- }
- public void setSalary(int salary)
- {
- this.salary = salary;
- }
- }
第一种方案:一个子类对应一张表 (TPS)
该方案是使继承体系中每一个子类都对应数据库中的一张表。示意图如下:
每一个子类对应的数据库表都包含了父类的信息,并且包含了自己独有的属性。每个子类对应一张表,而且这个表的信息是完备的,即包含了所有从父类继承下来的属性映射的字段。这种策略是使用<union-subclass>标签来定义子类的。
配置People.hbm.xml文件:
- <?xml version="1.0"encoding="utf-8"?>
- <!DOCTYPE hibernate-mapping PUBLIC"-//Hibernate/Hibernate Mapping DTD 3.0//EN""http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
- <hibernate-mapping>
- <class name="com.bzu.hibernate.pojos.People"abstract="true">
- <id name="id" type="string">
- <column name="id"></column>
- <generator class="uuid"></generator>
- </id>
- <property name="name" column="name"type="string"></property>
- <property name="sex" column="sex"type="string"></property>
- <property name="age" column="age"type="string"></property>
- <property name="birthday" column="birthday"type="timestamp"></property>
- <!--
- <union-subclass name="com.bzu.hibernate.pojos.Student"table="student">
- <property name="cardId" column="cardId"type="string"></property>
- </union-subclass>
- <union-subclass name="com.bzu.hibernate.pojos.Teacher"table="teacher">
- <property name="salary" column="salary"type="integer"></property>
- </union-subclass>
- -->
- </class>
- <union-subclass name="com.bzu.hibernate.pojos.Student"
- table="student"extends="com.bzu.hibernate.pojos.People">
- <property name="cardId" column="cardId"type="string"></property>
- </union-subclass>
- <union-subclass name="com.bzu.hibernate.pojos.Teacher"
- table="teacher"extends="com.bzu.hibernate.pojos.People">
- <property name="salary" column="salary"type="integer"></property>
- </union-subclass>
- </hibernate-mapping>
以上配 置是一个子类一张表方案的配置,<union-subclass>标签是用于指示出该hbm文件所表示的类的子类,如People类有两个子 类,就需要两个<union-subclass>标签以此类推。<union-subclass>标签的"name"属性用于指 定子类的全限定名称,"table"属性用于指定该子类对应的表的名称,"extends"属性用于指定该子类的父类,注意该属性与<union-subclass>标签的位置有关,若<union-subclass>标签作为<class>标签的子标签,则"extends"属性可以不设置,否则需要明确设置"extends"属性。<class>标签中的"abstract"属性如果值为true则,不会生成表结构。如果值为false则会生成表结构,但是不会插入数据。
根据People.hbm.xml生成表结构,可以看到一个子类对应一张表:
- <span xmlns="http://www.w3.org/1999/xhtml" style="">drop table if exists student
- drop table if exists teacher
- create table student (
- id varchar(255) not null,
- name varchar(255),
- sex varchar(255),
- age varchar(255),
- birthday datetime,
- cardId varchar(255),
- primary key (id)
- )
- create table teacher (
- id varchar(255) not null,
- name varchar(255),
- sex varchar(255),
- age varchar(255),
- birthday datetime,
- salary integer,
- primary key (id)
- )</span>
第二种方案:使用一张表表示所有继承体系下的类的属性的并集(TPH)
这种策略是使用<subclass>标签来实现的。因为类继承体系下会有许多个子类,要把多个类的信息存放在一张表中,必须有某种机制来区 分哪些记录是属于哪个类的。Hibernate中的这种机制就是,在表中添加一个字段,用这个字段的值来进行区分。在表中添加这个标示列使 用<discriminator>标签来实现。
该策略的示意图:
将继承体系中的所有类信息表示在同一张表中后,只要是这个类没有的属性会被自动赋上null。
配置People.hbm.xml:
- <?xml version="1.0"encoding="utf-8"?>
- <!DOCTYPE hibernate-mapping PUBLIC"-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
- <hibernate-mapping>
- <class name="com.bzu.hibernate.pojos.People"table="people">
- <id name="id" type="string">
- <column name="id"></column>
- <generator class="uuid"></generator>
- </id>
- <discriminator column="peopleType"type="string"></discriminator>
- <property name="name" column="name"type="string"></property>
- <property name="sex" column="sex"type="string"></property>
- <property name="age" column="age"type="string"></property>
- <property name="birthday" column="birthday"type="timestamp"></property>
- <subclass name="com.bzu.hibernate.pojos.Student"discriminator-value="student">
- <property name="cardId" column="cardId"type="string"></property>
- </subclass>
- <subclass name="com.bzu.hibernate.pojos.Teacher"discriminator-value="teacher">
- <property name="salary" column="salary"type="string"></property>
- </subclass>
- </class>
- </hibernate-mapping>
<discriminator>标签用于在表中创建一个标识列,其"column"属性指定标识列的列名,"type"指定了标识列的类型。<subclass>标签用于指 定该HBM文件代表类的子类,有多少子类就有多少个该标签,其"name"属性指定子类的名称,"discriminator-value"属性指定该子类的数据的标识列的值是什么,其"extends"属性与<union-subclass>的"extends"属性用法一致。
下面来看一下根据People.hbm.xml生成表结构,可以看到一张表将继承体系下的所有信息都包含了,其中"peopleType"为标识列:
- drop table if exists people
- createtable people (
- idvarchar(255) not null,
- peopleType varchar(255) not null,
- namevarchar(255),
- sexvarchar(255),
- agevarchar(255),
- birthday datetime,
- cardIdvarchar(255),
- salaryvarchar(255),
- primary key (id)
- )
第三种方案:每个子类使用一张表只存储它特有的属性,然后与父类所对应的表以一对一主键关联的方式关联起来。(TPC)
这种策略是使用<joined-subclass>标签来定义子类的。父类、子类都对应一张数据库表。在父类对应的数据库表中,它存储了所 有记录的公共信息,实际上该父类对应的表会包含所有的记录,包括父类和子类的记录;在子类对应的数据库表中,这个表只定义了子类中所特有的属性映射的字 段。子类对应的数据表与父类对应的数据表,通过一对一主键关联的方式关联起来。
这种策略的示意图:
people表中存储了子类的所有记录,但只记录了他们共有的信息,而他们独有的信息存储在他们对应的表中,一条记录要获得其独有的信息,要通过people记录的主键到其对应的子表中查找主键值一样的记录然后取出它独有的信息。
配置People.hbm.xml:
- <?xml version="1.0"encoding="utf-8"?>
- <!DOCTYPE hibernate-mapping PUBLIC"-//Hibernate/Hibernate Mapping DTD 3.0//EN""http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
- <hibernate-mapping>
- <class name="com.suxiaolei.hibernate.pojos.People"table="people">
- <id name="id" type="string">
- <column name="id"></column>
- <generator class="uuid"></generator>
- </id>
- <property name="name" column="name"type="string"></property>
- <property name="sex" column="sex"type="string"></property>
- <property name="age" column="age"type="string"></property>
- <property name="birthday" column="birthday"type="timestamp"></property>
- <joined-subclassnamejoined-subclassname="com.suxiaolei.hibernate.pojos.Student"table="student">
- <key column="id"></key>
- <property name="cardId" column="cardId"type="string"></property>
- </joined-subclass>
- <joined-subclassnamejoined-subclassname="com.suxiaolei.hibernate.pojos.Teacher"table="teacher">
- <key column="id"></key>
- <property name="salary" column="salary"type="integer"></property>
- </joined-subclass>
- </class>
- </hibernate-mapping>
<joined-subclass>标签需要包含一个key标签,这个标签指定了子类和父类之间是通过哪个字段来关联的。
根据People.hbm.xml生成表结构, 可以看到,父类对应的表保存公有信息,子类对应的表保存独有信息,子类和父类对应的表使用一对一主键关联的方式关联起来
- drop table if exists people
- drop table if exists student
- droptable if exists teacher
- create table people (
- id varchar(255) not null,
- name varchar(255),
- sex varchar(255),
- age varchar(255),
- birthday datetime,
- primary key (id)
- )
- create table student (
- id varchar(255) not null,
- cardId varchar(255),
- primary key (id)
- )
- create table teacher (
- id varchar(255) not null,
- salary integer,
- primary key (id)
- )
- alter table student
- add index FK8FFE823BF9D436B1 (id),
- add constraint FK8FFE823BF9D436B1
- foreign key (id)
- references people (id)
- alter table teacher
- add index FKAA31CBE2F9D436B1 (id),
- add constraint FKAA31CBE2F9D436B1
- foreign key (id)
- references people (id)
三种映射方式的比较和选择---三种方式的优缺点
为了方便说明为三种方式按顺序标号为[1][2][3]。
【1】:优点:数据结构清晰
缺点:两个子表的主键不能重复,不能使用数据库的自增方式生成主键。
【2】:优点:查询效率高,符合数据库设计粗粒度(推荐)
缺点:存在冗余字段,有些字段是子类不具有的属性。
【3】:优点:数据结构清晰,没有冗余
缺点:类的继承层次比较多的话,造成生成的表也比较多,增删改查效率低下
总结:
1. 通过总结这三种方式的优缺点发现,使用继承树生成一张表的方式似乎更符合数据库粗粒度设计的原则。当然数据量非常大的话也可以考虑每个类生成一张表的成方式
2.程序的对象模型没有发生变化,变化的是关系模型。
这就是hibernate的好处,想改变关系模型,只需要改变映射文件即可。