1. 程式人生 > >Hibernate 單向多對一、單向一對多、雙向一對多關聯關係詳解

Hibernate 單向多對一、單向一對多、雙向一對多關聯關係詳解

一對多關係是最普遍的對映關係。比如部門和職工
一對多:從部門的角度,一個部門對應多個職工
多對一:從職工的角度,多個職工對應一個部門

資料庫表中結構:
表 department:did departname
表 Employee:eid   ename   esex   did

工具類:SessionFactoryUtils .java,用來建立session工廠,後面的測試類的程式碼中會呼叫這個工具類的方法

public class SessionFactoryUtils {
    private SessionFactory factory;
    private
static SessionFactoryUtils factoryUtils; // 單例模式:把構造方法設定為私有的,說明不可以new這個類的例項。 private SessionFactoryUtils() { } // 通過定義這個類的靜態方法,並且返回型別與這個類的型別一樣,來實現對這個類的訪問 public static SessionFactoryUtils getInstance() { if (factoryUtils == null) { factoryUtils = new SessionFactoryUtils(); } return
factoryUtils; } public SessionFactory openSessionFactory() { if (factory == null) { //載入主配置檔案 Configuration configuration = new Configuration().configure(); //建立工廠 factory = configuration.buildSessionFactory(); } return factory; } }

單向多對一:

首先兩個實體類,一個部門類,一個職工類:

public class Department {
    private Integer did;
    private String departname;
    //get和set方法,這裡就不貼出來了,自己引入即可
}
public class Employee {
    private Integer eid;
    private String ename;
    private String esex;
    private Department department;
}



Employee類的對映檔案:Employee.hbm.xml:多的一方,需要維護雙方關係,所以裡面配置有一的一方的引用。

<hibernate-mapping>
    <class name="org.danni.model.entity.Employee">
        <!-- 設定主鍵 -->
        <id name="eid" column="eid">
            <!-- 設定為native之後,主鍵會按照本來的順序進行增長(比如之前23但是刪除了,現在就從24開始) -->
            <generator class="native"></generator>
        </id>

        <!-- 設定屬性 -->
        <property name="ename" column="ename"></property>
        <property name="esex" column="esex"></property>

        <many-to-one name="department"  cascade="all" column="did" 
            class="org.danni.model.entity.Department"></many-to-one>
    </class>
</hibernate-mapping>

many-to-one的常用屬性:
name:對映類屬性的名稱(必須)
class:關聯類的完全限定名
column:關聯的欄位
not-null:設定關聯的欄位的值是否可以為空。預設值false
lazy:指定關聯物件是否使用延遲載入以及延遲載入的策略。lazy屬性有三個取值:false、proxy、no-proxy。預設值proxy
fetch:設定抓取資料的策略。預設值是select
many-to-one沒有inverse屬性,因為關係的維護是多的一方,不可能放棄對關係的維護。



Department.hbm.xml對映檔案:一的一方,不需要維護關係,所以和普通的配置一樣既可以了。

<hibernate-mapping>
    <class name="org.danni.model.entity.Department">
        <id name="did" column="did">
            <!-- 設定成increment之後,會從資料庫中查詢主鍵id最大的值,然後+1進行儲存 -->
            <generator class="increment"></generator>
        </id>

        <property name="departname" column="departname"></property>
    </class>
</hibernate-mapping>



多對一測試類:Junit Test Case:ManyToOneTest .java

public class ManyToOneTest {
    private SessionFactory factory;

    @Before
    public void init() {
        //呼叫前面的工具類來建立並開啟session工廠
        factory = SessionFactoryUtils.getInstance().openSessionFactory();
    }

    @Test
    public void add() {
        // 所有的公用的都是一個session,它的內容也不會清空。事務是提交之後就關閉了。
        Session session = factory.openSession();
        Transaction ts = session.beginTransaction();

        Department department = new Department();
        department.setDepartname("售後部");

        Employee e1 = new Employee();
        e1.setEname("呵呵");
        e1.setEsex("男");
        e1.setDepartment(department);

        Employee e2 = new Employee();
        e2.setEname("嘻嘻");
        e2.setEsex("女");
        e2.setDepartment(department);

        try {
            // 單向:也就是說也可通過save員工之前把部門save到資料庫,但是不能通過save部門達到save員工的作用
            session.save(e1);           //因為設定了級聯cascade為all,所以在儲存Employee的時候會同時儲存Department
            session.save(e2);
                ts.commit();           //增、刪、改都徐亞用到事務,通過事務來commit,查詢則不需要用到事務
        } catch (Exception e) {
            ts.rollback();
        }
    }
}

控制檯會輸出:

Hibernate: select max(did) from Department
Hibernate: insert into Department (departname, did) values (?, ?)
Hibernate: insert into Employee (ename, esex, did) values (?, ?, ?)
Hibernate: insert into Employee (ename, esex, did) values (?, ?, ?)


之所以叫它多對一,是因為他們之間的關係是由多的一方來維護的。我們根據職工Employee的資訊就能夠知道它對應的Department的資訊。

單向多對一:我們可以通過一個員工知道它屬於那個部門,而不需要知道一個部門裡 有哪些員工,這就是所謂的單向的。
可以通過儲存員工的資料的同時達到儲存部門資訊的效果,但是不能通過儲存部門資訊而儲存員工資料,這也是單向的。

多對一對映原理:在多的一端加入一個外來鍵指向一的一端,它維護的關係多指向一。
一的一方,不需要維護關係,所以和普通的配置一樣既可以了。
多的一方,需要維護雙方關係,所以裡面配置有一的一方的引用。

單向一對多

首先兩個實體類,一個部門類,一個職工類:
一個部門會對應多個員工物件,因此設定了一個員工物件的集合。這個集合為什麼要用Set型別而不用List型別的呢?因為List集合是可儲存重複的元素的,而Set中元素具有唯一性,不會去儲存重複的元素,因此這個地方我們採用Set集合

Department實體類

public class Department {
    private Integer did;
    private String departname;
    private Set<Employee> employeeSet = new HashSet<Employee>();
    //get和set方法,這裡就不貼出來了,自己引入即可
}


Employee實體類

public class Employee {
    private Integer eid;
    private String ename;
    private String esex;
}


Employee類的對映檔案:Employee.hbm.xml

<hibernate-mapping>
    <class name="org.danni.model.entity.Employee">
        <!-- 設定主鍵 -->
        <id name="eid" column="eid">
            <!-- 設定為native之後,主鍵會按照本來的順序進行增長(比如之前23但是刪除了,現在就從24開始) -->
            <generator class="native"></generator>
        </id>

        <!-- 設定屬性 -->
        <property name="ename" column="ename"></property>
        <property name="esex" column="esex"></property>
    </class>
</hibernate-mapping>


Department.hbm.xml對映檔案

<hibernate-mapping>
    <class name="org.danni.model.entity.Department">
        <id name="did" column="did">
            <!-- 設定成increment之後,會從資料庫中查詢主鍵id最大的值,然後+1進行儲存 -->
            <generator class="increment"></generator>
        </id>

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

        <!-- 
            inverse:預設為false, 指關聯關係的控制權由自己來維護
            inverse設定為true的時候,就說明關聯在多的那一方被維護 (一般設定在多的一方去維護,效率會跟高一點)
        -->
        <set name="employeeSet" cascade="all" lazy="false">
            <key column="did"></key>
            <one-to-many class="org.danni.model.entity.Employee"/>
        </set>
    </class>
</hibernate-mapping>


One-To-Many測試類(會有問題,我們這裡先看看是什麼問題以及出現這種問題的原因是什麼,後面會說明解決辦法)

public class OneToManyTest {
    private SessionFactory factory;

    @Before
    public void init() {
        factory = SessionFactoryUtils.getInstance().openSessionFactory();
    }

    @Test
    public void add() {
        Session session = factory.openSession();
        Transaction ts = session.beginTransaction();

        Department d = new Department();
        d.setDepartname("財務部");

        Employee e1 = new Employee();
        e1.setEname("呵呵");
        e1.setEsex("男");

        Employee e2 = new Employee();
        e2.setEname("嘻嘻");
        e2.setEsex("女");

        d.getEmployeeSet().add(e1);
        d.getEmployeeSet().add(e2);

        try {
            session.save(d);
            ts.commit();
        } catch (Exception e) {
            ts.rollback();
        }
    }
}


控制檯輸出:

Hibernate: select max(did) from Department
Hibernate: insert into Department (departname, did) values (?, ?)
Hibernate: insert into Employee (ename, esex) values (?, ?)
Hibernate: insert into Employee (ename, esex) values (?, ?)
Hibernate: update Employee set did=? where eid=?
Hibernate: update Employee set did=? where eid=?


單向一對多:實體之間關係由”一”的一端載入”多”的一端,關係由”一”的一端來維護。也就是說在”一”的一端持有”多”的一端的集合。
我們在載入”一”的時候把”多”也載入進來了,也就是在部門中維護職工的集合。我們可以根據部門找到屬於這些部門的所有職工。但是不能根據職工找到它所屬的部門。

為什麼會有5條sql語句,進行insert之後為什麼還要update呢?
因為單向一對多關係是在”一”的一方維護資料,多的一方Employee並不知道有Department的存在。所以在儲存Employee的時候,關係欄位did是為null的。如果把did欄位設定為非空,則無法儲存資料。
因為Employee不維護關係,而是由Department來維護關係,則在對Department進行操作的時候,就會發出多餘的update語句,去維持Department與Employee的關係,這樣載入Department的時候才會把該Department對應的職工載入進來。所以會多兩條udate語句。這樣的效率是非常低的。

為了提高效率,我們一般會採用雙向一對多關聯的方式。

雙向一對多

所謂的雙向一對多關聯,同時配置單向多對一和單向一對多就可以構成雙向一對多關聯關係。它在解決單向一對多關係存在的缺陷起到了一定的修補作用。
Department實體類

public class Department {
    private Integer did;
    private String departname;
    private Set<Employee> employeeSet = new HashSet<Employee>();
    //get和set方法,這裡就不貼出來了,自己引入即可
}


Employee實體類

public class Employee {
    private Integer eid;
    private String ename;
    private String esex;
    private Department department;
}


Employee類的對映檔案:Employee.hbm.xml

<hibernate-mapping>
    <class name="org.danni.model.entity.Employee">
        <!-- 設定主鍵 -->
        <id name="eid" column="eid">
            <!-- 設定為native之後,主鍵會按照本來的順序進行增長(比如之前23但是刪除了,現在就從24開始) -->
            <generator class="native"></generator>
        </id>

        <!-- 設定屬性 -->
        <property name="ename" column="ename"></property>
        <property name="esex" column="esex"></property>

        <many-to-one name="department"  cascade="all" column="did" 
            class="org.danni.model.entity.Department"></many-to-one>
    </class>
</hibernate-mapping>


Department.hbm.xml對映檔案

<hibernate-mapping>
    <class name="org.danni.model.entity.Department">
        <id name="did" column="did">
            <!-- 設定成increment之後,會從資料庫中查詢主鍵id最大的值,然後+1進行儲存 -->
            <generator class="increment"></generator>
        </id>

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

        <!-- 
            inverse:預設為false, 指關聯關係的控制權由自己來維護
            inverse設定為true的時候,就說明關聯在多的那一方被維護 (一般設定在多的一方去維護,效率會跟高一點)
        -->
        <set name="employeeSet" cascade="all" inverse="true" lazy="false">
            <key column="did"></key>
            <one-to-many class="org.danni.model.entity.Employee"/>
        </set>
    </class>
</hibernate-mapping>


One-To-ManyDouble測試類(會有問題,我們這裡先看看是什麼問題以及出現這種問題的原因是什麼,後面會說明解決辦法)

public class OneToManyTestDouble {
    private SessionFactory factory;

    @Before
    public void init() {
        factory = SessionFactoryUtils.getInstance().openSessionFactory();
    }

    @Test
    public void add() {
        Session session = factory.openSession();
        Transaction ts = session.beginTransaction();

        Department d = new Department();
        d.setDepartname("財務部");

        Employee e1 = new Employee();
        e1.setEname("呵呵");
        e1.setEsex("男");

        Employee e2 = new Employee();
        e2.setEname("嘻嘻");
        e2.setEsex("女");

        d.getEmployeeSet().add(e1);
        d.getEmployeeSet().add(e2);

        try {
            session.save(d);
            ts.commit();
        } catch (Exception e) {
            ts.rollback();
        }
    }
}


控制檯輸出:可以發現只有三條sql語句,沒有那2條update語句了

Hibernate: select max(did) from Department
Hibernate: insert into Department (departname, did) values (?, ?)
Hibernate: insert into Employee (ename, esex, did) values (?, ?, ?)
Hibernate: insert into Employee (ename, esex, did) values (?, ?, ?)

只能在set上面設定inverse屬性值。不能在many-to-one上面設定。設定set標籤的inverser屬性值為true,即表示轉交控制權,把控制權轉交給多的一方,即控制權就在Employee的那一方。inverse屬性預設值是false。

因為是雙向操作,所以儲存任何一方都能成功,但是推薦儲存”一”的一方,並在”一”的一方交出控制權,這樣會提高效率

hibernate僅僅會格局主控方物件的狀態來同步更新資料庫。

只能在set上面設定inverse屬性值。many-to-one上沒有這個屬性。