繼承 - Table per subclass


接續 上一個主題 ,在使用Table per class hierarchy映射繼承關係時,會有大量的欄位有NULL的情況,好處是使用一個表格,查詢時只需一次SQL。

Table per subclass的繼承映射方式,給予父類與子類分別的表格,而父類與子類對應的表格透過外鍵來產生關聯,具體的說,User類別、 DefaultUser類別與PowerUser類別所映射的表格如下:
Table per subclass


其中user表格的id與defaultuser及poweruser的id 一致,具體的說,在儲存DefaultUser實例時,id與name屬性記錄在user表格中,而someProperty記錄在 defaultuser中,假設user表格的id值為1,則defaultuser表格對應的該筆記錄其id值也會為一。

可以使用以下的SQL建立資料表:
create table T_Defaultuser (
    id bigint not null,
    someProperty varchar(255),
    primary key (id)
)

create table T_Poweruser (
    id bigint not null,
    otherProperty varchar(255),
    primary key (id)
)

create table T_USER (
    id bigint not null auto_increment,
    name varchar(255),
    primary key (id)
)

alter table T_Defaultuser
    add index id (id),
    add constraint id
    foreign key (id)
    references T_USER (id)

alter table T_Poweruser
    add index id (id),
    add constraint id
    foreign key (id)
    references T_USER (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>

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

<joined-subclass name="DefaultUser"
table="T_Defaultuser">
<key column="id" foreign-key="id"/>
<property name="someProperty" column="someProperty" />
</joined-subclass>

<joined-subclass name="PowerUser"
table="T_Poweruser">
<key column="id" foreign-key="id"/>
<property name="otherProperty" column="otherProperty" />
</joined-subclass>
</class>

</hibernate-mapping>

<joined-subclass>指明了子類別與所對應的表格,<key column>指明子類別的對應表格中,哪一個欄位要與父類別的主鍵一致,來看Hibernate儲存時的例子:
Hibernate:
    insert
    into
        T_USER
        (name)
    values
        (?)
Hibernate:
    insert
    into
        T_Poweruser
        (otherProperty, id)
    values
        (?, ?)
Hibernate:
    insert
    into
        T_USER
        (name)
    values
        (?)
Hibernate:
    insert
    into
        T_Defaultuser
        (someProperty, id)
    values
        (?, ?)

來看Hibernate查詢時的例子:
Hibernate:
    select
        user0_.id as id0_,
        user0_.name as name0_,
        user0_1_.someProperty as someProp2_1_,
        user0_2_.otherProperty as otherPro2_2_,
        case
            when user0_1_.id is not null then 1
            when user0_2_.id is not null then 2
            when user0_.id is not null then 0
        end as clazz_
    from
        T_USER user0_
    left outer join
        T_Defaultuser user0_1_
            on user0_.id=user0_1_.id
    left outer join
        T_Poweruser user0_2_
            on user0_.id=user0_2_.id

使用 繼承 - Table per concrete class 中的儲存程式片段,則查詢表格時可以發現以下的結果:
mysql> select * from user;
+-----+-------------+
| id  | name        |
+-----+-------------+
|  1  | caterpillar |
|  2  | Bush        |
+-----+-------------+
2 rows in set (0.00 sec)

mysql> select * from defaultuser;
+----+------------------+
| id | someProperty     |
+----+------------------+
|  2 | hu....hu...      |
+----+------------------+
1 row in set (0.00 sec)

mysql> select * from poweruser;
+----+-------------------+
| id | otherProperty     |
+----+-------------------+
|  1 | Bla...Bla...      |
+----+-------------------+
1 row in set (0.00 sec)


仔細觀察一下,看看defaultuser與poweruser表格中的id各自是對應於user表格中的哪筆資料。

效能是這個映射類型需要考量的,在複雜的類別繼承下,新增資料必須對多個表格進行,而查詢時,跨越多個表格的join也可能引發效能上的問題。

如果您需要多型查詢,而子類別相對來說有較多新增的屬性,則可以使用這種映射方式。