在面向对象的程序领域中,类与类之间是有继承关系的,例如Java世界中只需要extends关键字就可以确定这两个类的父子关系,但是在关系数据库的世界中,表与表之间没有任何关键字可以明确指明这两张表的父子关系,表与表是没有继承关系这样的说法的。为了将程序领域中的继承关系反映到数据中,Hibernate为我们提供了3中方案:
第一种方案:一个子类对应一张表。
第二种方案:使用一张表表示所有继承体系下的类的属性的并集。
第三种方案:每个子类使用一张表只存储它特有的属性,然后与父类所对应的表以一对一主键关联的方式关联起来。
现在假设有People、Student、Teacher三个类,父类为People,Student与Teacher为People的父类,代码如下:
People类:
- public class People
- {
-
- private String id;
- private String name;
- private String sex;
- private String age;
- private Timestamp birthday;
-
- }
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
- {
-
- private int salary;
- public int getSalary()
- {
- return salary;
- }
- public void setSalary(int salary)
- {
- this.salary = salary;
- }
- }
第一种方案:一个子类对应一张表
该方案是使继承体系中每一个子类都对应数据库中的一张表。
示意图如下:
每一个子类对应的数据库表都包含了父类的信息,并且包含了自己独有的属性。每个子类对应一张表,而且这个表的信息是完备的,即包含了所有从父类继承下来的属性映射的字段。这种策略是使用<union-subclass>标签来定义子类的。
配置People.hbm.xml文件:(注意:这里都写在了一个People的配置文件里,也可以分别写在三个配置文件,即Student.hbm.xml、Teacher.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" 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.suxiaolei.hibernate.pojos.Student" table="STUDENT">
- <property name="cardId" column="CARDID" type="string"></property>
- </union-subclass>
- <union-subclass name="com.suxiaolei.hibernate.pojos.Teacher" table="TEACHER">
- <property name="salary" column="SALARY" type="integer"></property>
- </union-subclass>
- -->
- </class>
-
- <union-subclass name="com.suxiaolei.hibernate.pojos.Student"
- table="STUDENT" extends="com.suxiaolei.hibernate.pojos.People">
- <property name="cardId" column="CARDID" type="string"></property>
- </union-subclass>
-
- <union-subclass name="com.suxiaolei.hibernate.pojos.Teacher"
- table="TEACHER" extends="com.suxiaolei.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生成表结构:
- 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)
- )
可以看到一个子类对应一张表。
第二种方案:使用一张表表示所有继承体系下的类的属性的并集
这种策略是使用<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.suxiaolei.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.suxiaolei.hibernate.pojos.Student" discriminator-value="student">
- <property name="cardId" column="CARDID" type="string"></property>
- </subclass>
- <subclass name="com.suxiaolei.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生成表结构:
- drop table if exists PEOPLE
- create table PEOPLE (
- ID varchar(255) not null,
- PEOPLETYPE varchar(255) not null,
- NAME varchar(255),
- SEX varchar(255),
- AGE varchar(255),
- BIRTHDAY datetime,
- CARDID varchar(255),
- SALARY varchar(255),
- primary key (ID)
- )
可以看到一张表将继承体系下的所有信息都包含了,其中"PEOPLETYPE"为标识列。
第三种方案:每个子类使用一张表只存储它特有的属性,然后与父类所对应的表以一对一主键关联的方式关联起来。
这种策略是使用<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-subclass name="com.suxiaolei.hibernate.pojos.Student" table="STUDENT">
- <key column="ID"></key>
- <property name="cardId" column="CARDID" type="string"></property>
- </joined-subclass>
- <joined-subclass name="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
- drop table 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)
可以看到,父类对应的表保存公有信息,子类对应的表保存独有信息,子类和父类对应的表使用一对一主键关联的方式关联起来。
在这三种方法中查询速度:第二种方案 > 第一种方案 > 第三种方案。解耦程度:第三种方案 > 第一种方案 > 第二种方案。没有那个绝对好与不好,只有最合适,我们根据需要选择一个最为恰当的即可。由于笔者更注重效率问题,所以个人比较倾向于第二种方案。