Hibernate——關係映射之多對一與一對多 & 懶加載 & 級聯操作

在Hibernate中,兩個相互有關聯的數據庫表在配置的時候需要額外的配置。

關係映射之多對一

多對一的經典案例:老師 <–> 部門,即一個老師只能對應一個部門,而一個部門對應多個老師。

下面通過示例說明多對一的關係。創建項目HibernateManyToOne

1.首先創建Teacher對象和Department對象。

package com.gavin.domain;

public class Teacher {
    private Integer id;
    private String name;
    private Department department;

    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 Department getDepartment() {
        return department;
    }

    public void setDepartment(Department department) {
        this.department = department;
    }
}
package com.gavin.domain;

import java.util.Set;

public class Department {
    private Integer id;
    private String name;
    private Set<Teacher> teacherSet;

    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 Set<Teacher> getTeacherSet() {
        return teacherSet;
    }

    public void setTeacherSet(Set<Teacher> teacherSet) {
        this.teacherSet = teacherSet;
    }
}

2.接着配置兩個domain對象的對象關係映射文件,Teacher.hbm.xml和Department.hbm.xml。

Department.hbm.xml文件如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD//EN"
        "http://hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.gavin.domain">
    <class name="com.gavin.domain.Department" table="department">
        <!--配置主鍵屬性-->
        <id name="id" column="did" type="java.lang.Integer">
            <generator class="increment"/>
        </id>
        <property name="name" column="name" length="64" not-null="true"/>
        <set name="teacherSet">
            <key column="did"/>
            <one-to-many class="com.gavin.domain.Teacher"/>
        </set>
    </class>
</hibernate-mapping>

可以看到,在Department的對象關係映射文件中,我們配置了<set>屬性,name爲teacherSet,正是Department對象中的集合屬性名,這個set屬性代表着一對多,它正是這個“多”存放的集合。接下來的<key column = 'did'>指定了Teacher對象引用自己的字段爲did。緊接着配置了<one-to-many>,其class屬性指定了一對多的“多”指的是哪一個對象。

另外,通過<generator class="increment">配置了主鍵的增長策略爲自增長。即我們在保存該對象的時候,可以不給其指定id,Hibernate會自動查出下一個id。

Teacher.hbm.xml文件如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD//EN"
        "http://hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.gavin.domain">
    <class name="com.gavin.domain.Teacher" table="teacher">
        <!--配置主鍵屬性-->
        <id name="id" column="sid" type="java.lang.Integer">
            <generator class="increment"/>
        </id>
        <property name="name" type="java.lang.String" column="name" length="64" not-null="true"/>
        <!---->
        <many-to-one name="department">
            <!--column表示將來映射出來的表的外鍵名-->
            <column name="did"/>
        </many-to-one>
    </class>
</hibernate-mapping>

可以看到與Department.hbm.xml相反,其配置了<many-to-one>,也就是多個Teacher對應一個Deparment

3.配置Hibernate

hibernate.cfg.xml文件如下:

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="connection.url">jdbc:mysql://localhost:3306/hibernate</property>
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="show_sql">true</property>
        <property name="connection.username">root</property>
        <property name="connection.password">gavin</property>

        <!-- DB schema will be updated if needed -->
        <property name="hbm2ddl.auto">update</property>
        <mapping resource="com/gavin/domain/Department.hbm.xml"/>
        <mapping resource="com/gavin/domain/Teacher.hbm.xml"/>
    </session-factory>
</hibernate-configuration>

其中,我們配置了<property name="hbm2ddl.auto">update</property>,讓Hibernate自動爲我們生成數據庫表。
並且配置了<property name="show_sql">true</property>,可以在控制檯輸出Hibernate使用的SQL語句。

4.測試案例

import com.gavin.domain.Department;
import com.gavin.domain.Teacher;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

public class Main {
    public static void main(String[] args) {
        SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
        Session session = sessionFactory.openSession();
        Transaction transaction = null;
        try {
            transaction = session.beginTransaction();

            // 創建一個Department對象
            Department department = new Department();
            department.setName("IT Department");

            // 創建一個Teacher對象
            Teacher teacher = new Teacher();
            teacher.setName("Gavin");
            // 設置其Department
            teacher.setDepartment(department);

            session.save(department);
            session.save(teacher);
            transaction.commit();
            session.close();
        } catch (Exception e) {
            if (transaction != null) {
                transaction.rollback();
            }
            throw new RuntimeException(e);
        }
    }
}

執行之後,控制檯輸出的語句如下:

Hibernate: create table department (did integer not null, name varchar(64) not null, primary key (did)) engine=InnoDB
Hibernate: create table teacher (sid integer not null, name varchar(64) not null, did integer, primary key (sid)) engine=InnoDB
Hibernate: alter table teacher add constraint FKadd6hrh8hv02jw7uss3wra6gb foreign key (did) references department (did)
Hibernate: select max(did) from department
Hibernate: select max(sid) from teacher
Hibernate: insert into department (name, did) values (?, ?)
Hibernate: insert into teacher (name, did, sid) values (?, ?, ?)

從SQL語句中可以看到,Hibernate先創建了兩張表department和teacher,然後給teacher表添加了外鍵引用。緊接着由於我們設置了ID的自增策略,所以Hibernate查詢了數據庫中的ID的最大值,最後保存了兩個對象,向數據庫添加了兩條記錄。

此時數據庫的變化如下:

這裏寫圖片描述

增加了department表和teacher表,並且兩個表都增加了一條記錄。

懶加載問題

因爲在這裏Teacher和Department有關聯關係,如果我們從數據庫獲取一個Teacher對象,那麼它對應的Department對象不會立馬讀出來,只有當你使用的時候纔會讀出來給你。

比如下例:

SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();

Teacher teacher = session.load(Teacher.class, 1);
System.out.println("teacher.getName() = " + teacher.getName());

此時hibernate的show_sql輸出的sql語句如下:

Hibernate: select teacher0_.sid as sid1_1_0_, teacher0_.name as name2_1_0_, teacher0_.did as did3_1_0_ from teacher teacher0_ where teacher0_.sid=?

此時只是查出了對應的Teacher對象,而其對應的Department對象並沒有通過數據庫讀取。只能當你調用這個Department對象的時候纔會再次讀取,這就是所謂的“懶加載”。如下例子,我們再訪問該Teacher對象的Department的名字:

SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();

Teacher teacher = session.load(Teacher.class, 1);
System.out.println("teacher.getName() = " + teacher.getName());
System.out.println("teacher.getDepartment().getName() = " + teacher.getDepartment().getName());

此時的sql語句爲:

Hibernate: select teacher0_.sid as sid1_1_0_, teacher0_.name as name2_1_0_, teacher0_.did as did3_1_0_ from teacher teacher0_ where teacher0_.sid=?
Hibernate: select department0_.did as did1_0_0_, department0_.name as name2_0_0_ from department department0_ where department0_.did=?

可以看出比剛剛多了一條語句,也就驗證了Hibernate的確是採用懶加載的方式加載關聯對象的。


懶加載也會涉及到一個問題,就是當你Session關閉了之後,對關聯對象的查詢就會出異常了。也就是說當你要使用關聯對象時,Session必須不能關閉,此時Hibernate才能從數據庫中查出來。

比如下例,我們通過一個方法獲取一個Teacher對象,然後在主方法中調用它:

public static Teacher getTeacher(int id) {
        SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
        Session session = sessionFactory.openSession();

        Teacher teacher = session.load(Teacher.class, id);
        session.close(); // 這裏關閉了Session

        return teacher;
}

public static void main(String[] args) {
        Teacher teacher = getTeacher(1);
        System.out.println("teacher.getName() = " + teacher.getName());
        System.out.println("teacher.getDepartment().getName() = " + teacher.getDepartment().getName());
}

第一行的輸出是完全正常的,teacher.getName()可以正常獲取,因爲Name只是Teacher對象的一般屬性,但是第二句話teacher.getDepartment().getName()就會出問題了,Department是Teacher對象的對象屬性,如果採用懶加載的話,getTeacher()方法返回的Teacher對象中還沒有包含Department對象,而且此時Session已經關閉了,所以Hibernate也無法再查出來了。出錯就是意料之中的了。

解決這個問題的方法有兩種:

  • 第一種方法是通過顯示地初始化代理對象

此時將getTeacher()方法更改如下:

public static Teacher getTeacher(int id) {
   SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
   Session session = sessionFactory.openSession();

   Teacher teacher = session.load(Teacher.class, id);
   // 初始化代理對象
   Hibernate.initialize(teacher.getDepartment());

   session.close(); // 這裏關閉了Session

   return teacher;
}

即在加載出Teacher對象的時候,通過Hibernate.initialize(teacher.getDepartment())方法顯示地初始化Department對象,此時與該Teacher關聯的Department對象也會從數據庫中讀出來。

  • 第二種方法就是讓Hibernate禁用懶加載

即在代理對象的對象關係映射文件中將該對象設置爲禁止懶加載,比如這裏的話,我們就在Department.hbm.xml文件中在class中加上lazy='false',配置如下:

<class name="com.gavin.domain.Department" lazy="false" table="department">

關係映射之一對多

一對多與上述多對一是相對應的。老師<-->部門是多對一,那麼部門<-->老師就是一對多,一對多的話需要在部門中配置相關集合(或列表)來保存所謂的“多”個對象,比如Set,在其Department.hbm.xml中也需要相應的配置:

public class Department implements Serializable{
    private int id;
    private String name;
    private Set<Teacher> set;

    //...省略setter和getter
}
<hibernate-mapping package="com.gavin.domain">
    <class name="com.gavin.domain.Department" lazy="false" table="department">
        <!--配置主鍵屬性-->
        <id name="id" column="did" type="java.lang.Integer">
            <generator class="increment"/>
        </id>
        <property name="name" column="name" length="64" not-null="true"/>
        <!--這裏就是配置的集合,即one-to-many關係-->
        <!--name表示集合名字-->
        <set name="teacherSet">
            <!--指定Teacher類對應的外鍵-->
            <key column="did"/>
            <!--class指的是一對多的多對應的是哪一個對象-->
            <one-to-many class="com.gavin.domain.Teacher"/>
        </set>
    </class>
</hibernate-mapping>

當配置好了一對多的關係之後,查詢集合就非常方便了,比如這裏我們查詢屬於部門1的所有老師:

public static void main(String[] args) {
    SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
    Session session = sessionFactory.openSession();
    Department department = session.load(Department.class, 1);
    for (Teacher teacher : department.getTeacherSet()) {
        System.out.println("teacher = " + teacher);
    }
}

級聯操作

同時呢,我們也可以通過級聯保存在保存部門的同時,也保存該部門對應的老師,當然級聯保存也需要相應的配置,如下的cascade屬性:

<set name="set" cascade="save-update">
    <key column="did"/>
    <one-to-many class="com.gavin.domain.Teacher"/>
</set>

此時我們做如下添加測試:

public static void main(String[] args) {
    SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
    Session session = sessionFactory.openSession();
    Transaction transaction = session.beginTransaction();

    Department department = new Department();
    department.setName("HR Department");

    Teacher teacher1 = new Teacher();
    teacher1.setName("XiaoMing");
    Teacher teacher2 = new Teacher();
    teacher2.setName("DaMing");

    Set<Teacher> set = new HashSet<>();
    set.add(teacher1);
    set.add(teacher2);
    department.setTeacherSet(set);

    session.save(department);
    transaction.commit();
}

運行之後的SQL語句輸出爲:

Hibernate: select max(did) from department
Hibernate: select max(sid) from teacher
Hibernate: insert into department (name, did) values (?, ?)
Hibernate: insert into teacher (name, did, sid) values (?, ?, ?)
Hibernate: insert into teacher (name, did, sid) values (?, ?, ?)
Hibernate: update teacher set did=? where sid=?
Hibernate: update teacher set did=? where sid=?

查看數據庫後,發現確實添加進去了:

這裏寫圖片描述


詳細說明:

cascade用來說明當對主對象進行某種操作時是否對其關聯的從對象也做類似的操作(比如在bbs項目中,主貼刪除之後回帖肯定也就刪除了)。

常用的cascade屬性有:

none,all,save-update,delete,lock,refresh,evict,replicate,persist,merge,delete-orphan(one-to-may)

一般對many-to-one,many-to-many不設置級聯,在one-to-one和one-to-many中設置級聯。

  • 在集合屬性和普通屬性中都能使用cascade
  • 一般將cascade配置在one-to-many中的one的一方,和one-to-one中的主對象一方

案例:在上述案例的基礎上配置級聯操作,當刪除一個部門的時候,同時也刪除該部門的所有老師。

即在casecade屬性上加上delete這個值,中間用逗號隔開。

<!--當保存或刪除部門對象時,同時保存或刪除該部門的老師-->
<set name="set" cascade="save-update,delete">
    <key column="did"/>
    <one-to-many class="com.gavin.domain.Teacher"/>
</set>

上述配置,可以在我們刪除或者保存一個部門對象時候,同時刪除或者保存這個部門的老師。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章