1. 程式人生 > >Hibernate繼承對映詳解

Hibernate繼承對映詳解

在面向物件的程式領域中,類與類之間是有繼承關係的,例如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;
    
    /*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所獨有的屬性*/
    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>

        <!-- 可單獨寫在Student.hbm.xml裡 -->
    <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>

        <!-- 可單獨寫在Teacher.hbm.xml裡 -->
    <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)

可以看到,父類對應的表儲存公有資訊,子類對應的表儲存獨有資訊,子類和父類對應的表使用一對一主鍵關聯的方式關聯起來。

在這三種方法中查詢速度:第二種方案 > 第一種方案 > 第三種方案。解耦程度:第三種方案 > 第一種方案 > 第二種方案。沒有那個絕對好與不好,只有最合適,我們根據需要選擇一個最為恰當的即可。由於筆者更注重效率問題,所以個人比較傾向於第二種方案。