在面向對象的程序領域中,類與類之間是有繼承關係的,例如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的好處,想改變關係模型,只需要改變映射文件即可。