1. 程式人生 > >Hibernate - 繼承關聯關係對映

Hibernate - 繼承關聯關係對映

對於面向物件的程式設計語言而言,繼承和多型是兩個最基本的概念。Hibernate 的繼承對映可以理解持久化類之間的繼承關係。例如:人和學生之間的關係。學生繼承了人,可以認為學生是一個特殊的人,如果對人進行查詢,學生的例項也將被得到。

Hibernate支援三種繼承對映策略:

  • 使用 subclass 進行對映: 對於繼承關係中的父類子類使用同一個表,這就需要在資料庫表中增加額外的區分子類型別的欄位。
  • 使用 joined-subclass 進行對映: 將域模型中的每一個子類分別對映到一個獨立的表中,通過關係資料模型中的外來鍵來描述表之間的繼承關係。這也就相當於按照域模型的結構來建立資料庫中的表,並通過外來鍵來建立表之間的繼承關係。
  • 使用 union-subclass 進行對映:域模型中的每個類分別單獨對映到一個單獨表,關係資料模型不用考慮域模型中的繼承關係和多型。

【1】採用 subclass 元素的繼承對映

採用 subclass 的繼承對映可以實現對於繼承關係中父類和所有子類使用同一張表。

因為父類和子類的例項全部儲存在同一個表中,因此需要在該表內增加一列,使用該列來區分每行記錄到底是哪個類的例項----這個列被稱為辨別者列(discriminator)。

在這種對映策略下,使用 subclass 來對映子類,使用 class 或 subclass 的 discriminator-value 屬性指定辨別者列的值。

所有子類定義的欄位都不能有非空約束。如果為那些欄位新增非空約束,那麼父類的例項在那些列其實並沒有值,這將引起資料庫完整性衝突,導致父類的例項無法儲存到資料庫中。

① Person類如下:

public class Person {
	
	private Integer id;
	private String name;
	private int age;
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	@Override
	public String toString() {
		return "Person [id=" + id + ", name=" + name + ", age=" + age + "]";
	}
	
}

② Student類如下

public class Student extends Person{

	private String school;

	public String getSchool() {
		return school;
	}

	public void setSchool(String school) {
		this.school = school;
	}

	@Override
	public String toString() {
		return "Student [school=" + school + ", toString()=" + super.toString() + "]";
	}

}

③ Person.hbm.xml如下:

<hibernate-mapping package="com.jane.subclass">

    <class name="Person" table="PERSONS" discriminator-value="PERSON">

        <id name="id" type="java.lang.Integer">
            <column name="ID" />
            <generator class="native" />
        </id>
        
        <!-- 配置辨別者列 -->
		<discriminator column="TYPE" type="string"></discriminator>

        <property name="name" type="java.lang.String">
            <column name="NAME" />
        </property>
        
        <property name="age" type="int">
            <column name="AGE" />
        </property>
        
        <!-- 對映子類 Student, 使用 subclass 進行對映 -->
        <subclass name="Student" discriminator-value="STUDENT">
        	<property name="school" type="string" column="SCHOOL"></property>
        </subclass>
        
    </class>
</hibernate-mapping>

④ 持久化測試

對於子類物件只需把記錄插入到一張資料表中,辨別者列由 Hibernate 自動維護。

程式碼如下:

	@Test
	public void testSave(){
		
		Person person = new Person();
		person.setAge(11);
		person.setName("AA");
		
		session.save(person);
		
		Student stu = new Student();
		stu.setAge(22);
		stu.setName("BB");
		stu.setSchool("JANE");
		
		session.save(stu);
		
	}

測試結果如下:

Hibernate: 
    
    create table PERSONS (
       ID integer not null auto_increment,
        TYPE varchar(255) not null,
        NAME varchar(255),
        AGE integer,
        SCHOOL varchar(255),
        primary key (ID)
    ) engine=InnoDB
Hibernate: 
    insert 
    into
        PERSONS
        (NAME, AGE, TYPE) 
    values
        (?, ?, 'PERSON')
Hibernate: 
    insert 
    into
        PERSONS
        (NAME, AGE, SCHOOL, TYPE) 
    values
        (?, ?, ?, 'STUDENT')

⑤ 查詢操作

程式碼如下:

	@Test
	public void testQuery(){
		List<Person> persons = session.createQuery("FROM Person").list();
		System.out.println(persons.size()); 
		
		List<Student> stus = session.createQuery("FROM Student").list();
		System.out.println(stus.size()); 
	}

測試結果如下:

Hibernate: 
    select
        person0_.ID as ID1_8_,
        person0_.NAME as NAME3_8_,
        person0_.AGE as AGE4_8_,
        person0_.SCHOOL as SCHOOL5_8_,
        person0_.TYPE as TYPE2_8_ 
    from
        PERSONS person0_
2
Hibernate: 
    select
        student0_.ID as ID1_8_,
        student0_.NAME as NAME3_8_,
        student0_.AGE as AGE4_8_,
        student0_.SCHOOL as SCHOOL5_8_ 
    from
        PERSONS student0_ 
    where
        student0_.TYPE='STUDENT'
1

查詢父類時,將所有屬性都查詢出來,查詢SQL中不需要where條件。查詢子類時,只查詢子類繼承父類屬性和子類自有屬性,並且查詢SQL使用了where條件。

不過相同的是,查詢父類或者子類只需要查詢同一張表。

SubClass缺點 :

  • 使用了辨別者列。
  • 子類獨有的欄位不能新增非空約束。
  • 若繼承層次較深, 則資料表的欄位也會較多。

【2】採用 joined-subclass 元素的繼承對映

採用 joined-subclass 元素的繼承對映可以實現每個子類對應一張表。

採用這種對映策略時,父類例項儲存在父類表中,子類例項由父類表和子類表共同儲存。

因為子類例項也是一個特殊的父類例項,因此必然也包含了父類例項的屬性。於是將子類和父類共有的屬性儲存在父類表中,子類增加的屬性,則儲存在子類表中。

在這種對映策略下,無須使用鑑別者列,但需要為每個子類使用 key 元素對映共有主鍵。

子類增加的屬性可以新增非空約束。因為子類的屬性和父類的屬性沒有儲存在同一個表中。

① 修改Person.hbm.xml如下:

<hibernate-mapping package="com.jane.joined.subclass">

    <class name="Person" table="PERSONS">
    
        <id name="id" type="java.lang.Integer">
            <column name="ID" />
            <generator class="native" />
        </id>
        
        <property name="name" type="java.lang.String">
            <column name="NAME" />
        </property>
        
        <property name="age" type="int">
            <column name="AGE" />
        </property>
        
        <joined-subclass name="Student" table="STUDENTS">
        	<key column="STUDENT_id"></key>
        	<property name="school" type="string" column="SCHOOL"></property>
        </joined-subclass>
        
    </class>
</hibernate-mapping>

② 持久化測試

程式碼如下:

	@Test
	public void testSave(){
		
		Person person = new Person();
		person.setAge(11);
		person.setName("AA");
		
		session.save(person);
		
		Student stu = new Student();
		stu.setAge(22);
		stu.setName("BB");
		stu.setSchool("JANE");
		
		session.save(stu);
		
	}

測試結果如下:

Hibernate: 
    
    create table PERSONS (
       ID integer not null auto_increment,
        NAME varchar(255),
        AGE integer,
        primary key (ID)
    ) engine=InnoDB
Hibernate: 
    
    create table STUDENTS (
       STUDENT_id integer not null,
        SCHOOL varchar(255),
        primary key (STUDENT_id)
    ) engine=InnoDB
Hibernate: 
    
    alter table STUDENTS 
       add constraint FK1v1p0142kff4r512ncp4prcy5 
       foreign key (STUDENT_id) 
       references PERSONS (ID)
Hibernate: 
    insert 
    into
        PERSONS
        (NAME, AGE) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        PERSONS
        (NAME, AGE) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        STUDENTS
        (SCHOOL, STUDENT_id) 
    values
        (?, ?)

可以看到,插入父類是直接插入了persons表;插入子類的時候則插入了兩張表中,將繼承父類的屬性插入到父表中,子類私有屬性插入到子類表中。且子類表中的Student_id與父類表中的主鍵有外來鍵約束關係。


③ 查詢獲取操作

程式碼如下:

	@Test
	public void testQuery(){
		List<Person> persons = session.createQuery("FROM Person").list();
		System.out.println(persons.size()); 
		
		List<Student> stus = session.createQuery("FROM Student").list();
		System.out.println(stus.size()); 
	}

測試結果如下:

Hibernate: 
    select
        person0_.ID as ID1_0_,
        person0_.NAME as NAME2_0_,
        person0_.AGE as AGE3_0_,
        person0_1_.SCHOOL as SCHOOL2_1_,
        case 
            when person0_1_.STUDENT_id is not null then 1 
            when person0_.ID is not null then 0 
        end as clazz_ 
    from
        PERSONS person0_ 
    left outer join
        STUDENTS person0_1_ 
            on person0_.ID=person0_1_.STUDENT_id//左外連線查詢
2
Hibernate: 
    select
        student0_.STUDENT_id as ID1_0_,
        student0_1_.NAME as NAME2_0_,
        student0_1_.AGE as AGE3_0_,
        student0_.SCHOOL as SCHOOL2_1_ 
    from
        STUDENTS student0_ 
    inner join
        PERSONS student0_1_ 
            on student0_.STUDENT_id=student0_1_.ID//內連線查詢
1

Person表查詢語句執行結果如下:
在這裡插入圖片描述

查詢父類記錄, 使用一個左外連線查詢; 對於子類記錄, 使用一個內連線查詢。

joined-subclass優點是:

  • 不需要使用了辨別者列.
  • 子類獨有的欄位能新增非空約束.
  • 沒有冗餘的欄位.

【3】採用 union-subclass 元素的繼承對映

採用 union-subclass 元素可以實現將每一個實體物件分別對映到一個獨立的表中。

子類增加的屬性可以有非空約束 — 即父類例項的資料儲存在父表中,而子類例項的資料儲存在子類表中。

子類例項的資料僅儲存在子類表中, 而在父類表中沒有任何記錄。

在這種對映策略下,子類表的欄位會比父類表的對映欄位要多,因為子類表的欄位等於父類表的欄位、加子類增加屬性的總和。

在這種對映策略下,既不需要使用鑑別者列,也無須使用 key 元素來對映共有主鍵。

使用 union-subclass 對映策略時不可使用 identity 的主鍵生成策略, 因為同一類繼承層次中所有實體類都需要使用同一個主鍵種子, 即多個持久化實體對應的記錄的主鍵應該是連續的。受此影響, 也不該使用 native 主鍵生成策略, 因為 native 會根據資料庫來選擇使用 identity 或 sequence。

① 修改Person.hbm.xml如下:

<hibernate-mapping package="com.jane.union.subclass">

    <class name="Person" table="PERSONS">

        <id name="id" type="java.lang.Integer">
            <column name="ID" />
            <generator class="increment" />
        </id>
        
        <property name="name" type="java.lang.String">
            <column name="NAME" />
        </property>
        
        <property name="age" type="int">
            <column name="AGE" />
        </property>
	
		<union-subclass name="Student" table="STUDENTS">
			<property name="school" column="SCHOOL" type="string"></property>
		</union-subclass>        
    </class>
</hibernate-mapping>

② 持久化測試

程式碼如下:

	@Test
	public void testSave(){
		
		Person person = new Person();
		person.setAge(11);
		person.setName("AA");
		
		session.save(person);
		
		Student stu = new Student();
		stu.setAge(22);
		stu.setName("BB");
		stu.setSchool("JANE");
		
		session.save(stu);
	}

測試結果如下:

Hibernate: 
    select
        max(ids_.mx) 
    from
        ( select
            max(ID) as mx 
        from
            STUDENTS 
        union
        select
            max(ID) as mx 
        from
            PERSONS 
    ) ids_
Hibernate: 
    insert 
    into
        PERSONS
        (NAME, AGE, ID) 
    values
        (?, ?, ?)
Hibernate: 
    insert 
    into
        STUDENTS
        (NAME, AGE, SCHOOL, ID) 
    values
        (?, ?, ?, ?)

可以看到父類和子類各自插入了一張表中,子類屬性包含父類屬性,子類和父類直接沒有外來鍵關係。


③ 查詢物件測試

程式碼如下:

	@Test
	public void testQuery(){
		List<Person> persons = session.createQuery("FROM Person").list();
		System.out.println(persons.size()); 
		
		List<Student> stus = session.createQuery("FROM Student").list();
		System.out.println(stus.size()); 
	}

測試結果如下:

Hibernate: 
    select
        person0_.ID as ID1_0_,
        person0_.NAME as NAME2_0_,
        person0_.AGE as AGE3_0_,
        person0_.SCHOOL as SCHOOL1_1_,
        person0_.clazz_ as clazz_ 
    from
        ( select
            ID,
            NAME,
            AGE,
            null as SCHOOL,
            0 as clazz_ 
        from
            PERSONS 
        union
        select
            ID,
            NAME,
            AGE,
            SCHOOL,
            1 as clazz_ 
        from
            STUDENTS 
    ) person0_
2
Hibernate: 
    select
        student0_.ID as ID1_0_,
        student0_.NAME as NAME2_0_,
        student0_.AGE as AGE3_0_,
        student0_.SCHOOL as SCHOOL1_1_ 
    from
        STUDENTS student0_
1

可以看到,查詢父類記錄, 需把父表和子表記錄彙總到一起再做查詢–如上所示做了子查詢union。查詢子類記錄則只需要從一張表中簡單查詢即可。


④ 更新物件測試

程式碼如下:

	@Test
	public void testUpdate(){
		String hql = "UPDATE Person p SET p.age = 20";
		session.createQuery(hql).executeUpdate();
	}

測試結果如下:

Hibernate: 
    create temporary table if not exists HT_PERSONS (ID integer not null) 
Hibernate: 
    insert 
    into
        HT_PERSONS
        select
            person0_.ID as ID 
        from
            ( select
                ID,
                NAME,
                AGE,
                null as SCHOOL,
                0 as clazz_ 
            from
                PERSONS 
            union
            select
                ID,
                NAME,
                AGE,
                SCHOOL,
                1 as clazz_ 
            from
                STUDENTS 
        ) person0_
Hibernate: 
    update
        PERSONS 
    set
        AGE=20 
    where
        (
            ID
        ) IN (
            select
                ID 
            from
                HT_PERSONS
        )
Hibernate: 
    update
        PERSONS 
    set
        AGE=20 
    where
        (
            ID
        ) IN (
            select
                ID 
            from
                HT_PERSONS
        )
Hibernate: 
    update
        STUDENTS 
    set
        AGE=20 
    where
        (
            ID
        ) IN (
            select
                ID 
            from
                HT_PERSONS
        )
Hibernate: 
    drop temporary table HT_PERSONS

更新是相當麻煩。

union-subclass優點:

  • 無需使用辨別者列.
  • 子類獨有的欄位能新增非空約束.

union-subclass缺點:

  • 存在冗餘的欄位
  • 若更新父表的欄位, 則更新的效率較低。

【4】三種繼承對映方式分析比較

比較方面 union-subclass subclass joined-subclass
建立關係模型的原則 每個具體類對應一張表 父類和子類共用一張表 父類單獨一張表,每個子類一張表,子類表不包含父類屬性列 ;子類和父類有外來鍵關聯關係
關係模型的優缺點 表中存在重複欄位 表中引入了區分子類的欄位;不能為子類屬性設定非空約束 符合關係模型設計原則,且無欄位重複
可維護性 如果需要對基類進行修改,則需要對基類及其子類所對應的所有表都進行修改 只需要修改一張表,很方便 維護比較方便,對每個類的修改只需要修改其所對應的表
靈活性 對映的靈活性很大,子類可以對包括基類屬性在內的每一個屬性進行單獨的配置 靈活性差,表中的冗餘欄位會隨著子類的增多而增多 靈活性很好,完全是參照物件繼承的方式進行對映配置
查詢的效能 對於子類的查詢只需要訪問單獨的表;但對父類的查詢需要訪問所有表 在任何情況下的查詢都需要處理這一張表 對於父類的查詢需要使用左外連線;而子類查詢則需要使用內連線
維護的效能 對於單個物件的持久化操作只需要處理一個表 對於單個物件的持久化操作只需要處理一個表 對於子類的持久化操作至少需要處理兩個表;對於父類的持久化操作只需要處理一個表