繼承 - Table per class hierarchy


接續 上一個主題,Table per concrete class的繼承映射方式是最簡單,但沒有效率(例如查詢同為User類型時,需要兩次SQL)且不易管理的映射方式,來看看繼承關係映射至關聯式資料庫 的第二種方式:Table per class hierarchy。這種方式使用一個表格儲存同一個繼承階層的所有類別,並使用額外的欄位來表示所記錄的是哪一個子類別的資料。

具體來說,對於繼承User類別的DefaultUser及PowerUser,可以設計以下的表格來儲存資料:
Table per class hierarchy


可以使用以下的SQL來建立表格:
create table T_USER (
    id bigint not null auto_increment,
    userType varchar(255) not null,
    name varchar(255),
    someProperty varchar(255),
    otherProperty varchar(255),
    primary key (id)
)


現在所決定的是,如果要儲存的資料是來自DefalutUser,則在userType記下"Default",如果儲存的資料來PowerUser,則 在userType記下"Power",由userType就可以在資料從資料庫取回時,決定其該封裝為DefaultUser或是PowerUser, 在使用Hibernate的話,這要在映射文件中使用<discriminator>等相關標籤來定義,例如:
  • User.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 package="onlyfun.caterpillar">

<class name="User" table="T_USER">

<id name="id" column="id">
<generator class="native"/>
</id>

<discriminator column="userType"/>

<property name="name" column="name"/>

<subclass name="DefaultUser"
discriminator-value="Default">
<property name="someProperty" column="someProperty" />
</subclass>

<subclass name="PowerUser"
discriminator-value="Power">
<property name="otherProperty" column="otherProperty"/>
</subclass>

</class>

</hibernate-mapping>

當然,別忘了在hibernate.cfg.xml 中指定映射文件:
  • hibernate.cfg.xml
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

<session-factory>
....

<!-- 物件與資料庫表格映射文件 -->
<mapping resource="onlyfun/caterpillar/User.hbm.xml"/>

</session-factory>

</hibernate-configuration>

使用 上一個主題 中的儲存程式的話,則Hibernate會使用以下的SQL來儲存資料:
Hibernate:
    insert
    into
        T_USER
        (name, otherProperty, userType)
    values
        (?, ?, 'Power')
Hibernate:
    insert
    into
        T_USER
        (name, someProperty, userType)
    values
        (?, ?, 'Default')


而實際上資料表會儲存以下的內容:
+----+-------------+-------------+-------------------+------------------+
| id | userType    | name        | someProperty      | otherProperty    |
+----+-------------+-------------+-------------------+------------------+
|  1 | Power       | caterpillar | NULL              | Bla...Bla...     |
|  2 | Default     | Bush        | hu....hu...       | NULL             |
+----+-------------+-------------+-------------------+-------------------+


缺點就是,因子類別屬性的不同,對映儲存時會有許多欄位沒有資料,但查詢效率較好,例如查詢User類型的資料時,只需一次SQL,如使用 上一個主題 中的查詢程式時,Hibernate會使用以下的SQL進行查詢:
Hibernate:
    select
        user0_.id as id0_,
        user0_.name as name0_,
        user0_.someProperty as someProp4_0_,
        user0_.otherProperty as otherPro5_0_,
        user0_.userType as userType0_
    from
        T_USER user0_

另一種方式是不使用一個欄位來記錄子類別的類型,這適用於您在使用一個舊資料庫,您無法新增欄位來記錄子類別類型,資料表格也許是這麼建立的:
create table T_USER (
    id bigint not null auto_increment,
    name varchar(255),
    someProperty varchar(255),
    otherProperty varchar(255),
    primary key (id)
)

則您可以這麼撰寫映射文件:
  • User.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 package="onlyfun.caterpillar">

<class name="User" table="T_USER">

<id name="id" column="id">
<generator class="native"/>
</id>

<discriminator
formula="case when someProperty is not null then 'Default' else 'Power' end"/>

<property name="name" column="name"/>

<subclass name="DefaultUser"
discriminator-value="Default">
<property name="someProperty" column="someProperty" />
</subclass>

<subclass name="PowerUser"
discriminator-value="Power">
<property name="otherProperty" column="otherProperty"/>
</subclass>

</class>

</hibernate-mapping>

無論是DefaultUser或PowerUser,儲存時直接儲存至表格,一個儲存的例子如下:
Hibernate:
    insert
    into
        T_USER
        (name, otherProperty)
    values
        (?, ?)
Hibernate:
    insert
    into
        T_USER
        (name, someProperty)
    values
        (?, ?)

在<discriminator>上,設定foumula屬性,根據傳回值為Default或Power,決定資料查詢回來後,該封裝為哪個類別的實例,一個查詢的例子如下:
Hibernate:
    select
        user0_.id as id0_,
        user0_.name as name0_,
        user0_.someProperty as someProp3_0_,
        user0_.otherProperty as otherPro4_0_,
        case
            when user0_.someProperty is not null then 'Default'
            else 'Power'
        end as clazz_
    from
        T_USER user0_

如果您需要多型查詢,而子類別屬性相對比較少時,可以使用這種映射方式。