Hibernate讀書筆記—–Hibernate的關聯映射之1-N關聯映射 – JAVA編程語言程序開發技術文章

   三、1—N
          對於1-N而言,它的持久化類發生瞭一點改變,持久化類裡需要使用集合屬性。因為1的一端需要訪問N的一端,而N的一端將以集合(Set)形式表現。
         1、單向1-N關聯
          對於單向的1-N關聯關系,隻需要在1的一端增加Set類型的屬性,該屬性記錄當前實體的關聯實體。
          同樣以員工-部門為例(Employee–>Department)。兩個持久化類如下:
          Department
[java] 
public class Department { 
    private Integer id; 
    private String name; 
    private Set<Employee> employees;          //關聯關系 
 
    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<Employee> getEmployees() { 
        return employees; 
    } 
 
    public void setEmployees(Set<Employee> employees) { 
        this.employees = employees; 
    } 
 

 
          Employee
[java] 
public class Employee { 
    private Integer id; 
    private String name; 
 
    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; 
    } 
 

 
         1.1基於無連接表的單向1-N關聯
         對於1-N的單向關聯,需要在1的一端增加對應的集合映射元素,如<set…/>、<bag…/>。在集合元素中需要增加<key…/>子元素,該子元素用來映射外鍵。同時集合元素中需要使用<one-to-many…/>元素來映射1-N關聯關系。
         下面是Department的映射文件Department.hbm.xml
[html] 
<hibernate-mapping package="com.hibernate.domain" > 
    <class name="Department" table="department"> 
        <id name="id" column="departmentID"> 
            <generator class="native" /> 
        </id> 
         
        <property name="name" column="departmentName" /> 
         
        <!– 映射集合屬合 –> 
        <set name="employees" inverse="true" > 
            <!– 指定關聯的外鍵列 –> 
            <key column="departmentID" /> 
            <!– 用以映射到關聯類屬性 –> 
            <one-to-many class="Employee"/> 
        </set>     
    </class> 
</hibernate-mapping> 

          對於上面的映射文件,映射<set…/>元素時並沒有指定cascade屬性,在默認的情況下,對主表實體的持久化操作不會級聯到從表實體。
         Employee.hbm.xml
[html] 
<hibernate-mapping package="com.hibernate.domain"> 
    <class name="Employee" table="employee"> 
        <id name="id" column="employeeID"> 
            <generator class="native" /> 
        </id> 
        <property name="name" column="employeeName" />     
    </class> 
</hibernate-mapping> 

          使用下面代碼來操作Department和Employee實體:保存一個Department實體和兩個Employee實體
[java] 
static void add(){ 
        Session session = HibernateUtil.getSession(); 
        Transaction tx = session.beginTransaction(); 
         
        Department department = new Department(); 
        department.setName("國防部"); 
         
        //建立兩個對象 
        Employee employee1 = new Employee(); 
        employee1.setName("chentmt1"); 
         
        Employee employee2 = new Employee(); 
        employee2.setName("chentmt2"); 
 
        Set<Employee> emps = new HashSet<Employee>(); 
        emps.add(employee1); 
        emps.add(employee2); 
         
        //設置Department和Employee之間的關聯關系 
        department.setEmployees(emps);        
     
        session.save(department);          //….1 
        session.save(employee2); 
        session.save(employee1); 
        tx.commit(); 
        session.close(); 
    } 

         分析上面代碼段:
         當程序運行到….1的時候,系統會持久化該對象:Department,而且這個對象已經關聯瞭兩個Employee對象。在這個時候Hibernate需要完成兩件事:
         1、執行insert語句想department表中插入一條記錄
         2、Hibernate試圖執行update語句,將當前的department表記錄關聯的employee表記錄的外鍵departmentID修改為該department記錄的主鍵的值。
         下面為上面代碼段的sql語句:
[sql]
Hibernate: insert into department (departmentName) values (?) 
 
Hibernate: insert into employee (employeeName) values (?) 
 
Hibernate: insert into employee (employeeName) values (?) 
 
Hibernate: update employee set departmentID=? where employeeID=? 
 
Hibernate: update employee set departmentID=? where employeeID=? 
          從上面的sql語句中我們可以看到雖然程序僅僅需要為Department實體增加一個關聯Employee實體,但是Hibernate會采用兩條SQL語句來完成:一條inset語句插入一個條外鍵為null的employee記錄,一條update語句修改插入的employee記錄。造成這個問題的根本原因就在於:從Department到Employee的關聯並沒有被當中為Department對象狀態的一部分(程序是通過吧Employee實體添加到Department實體的集合屬性中,而Employee實體並不知道她所關聯的Department實體),因而Hibernate無法在執行insert語句為該外鍵指定值。
         為瞭解決這個問題,程序必須在持久化Employee實體之前,讓Employee實體能夠知道它所關聯的Department實體,也就是通過employee.setDepartment(department);方法建立關聯關系,但是這個時候就已經變成瞭1-N的雙向關聯。所以,盡量少用1-N的單向關聯,而改用1-N的雙向關聯。

         1.2基於有連接表的單向1-N關聯
         對於有連接表的1-N關聯映射,映射文件不再使用<one-to-many…./>元素映射關聯實體,而是使用<many-to-many…/>元素,但是為瞭保證當前實體是1的一端,我們需要增加一個屬性:unique="true"。
         所以Department.hbm.xml配置文件如下:
[html] 
<hibernate-mapping package="com.hibernate.domain" > 
    <class name="Department" table="department"> 
        <id name="id" column="departmentID"> 
            <generator class="native" /> 
        </id> 
        <property name="name" column="departmentName" /> 
         
        <set name="employees" inverse="true" table="department_employee"> 
            <!– 指定關聯的外鍵列 –> 
            <key column="departmentID" /> 
            <!– 用以映射到關聯類屬性 –> 
            <many-to-many class="Employee" column="employeeID" unique="true"/> 
        </set> 
    </class> 
</hibernate-mapping>  
          Employee.hbm.xml配置文件保持不變
          如果我們依然通過使用操作方法:保存一個Department、兩個Employee,這個時候,系統應該是產生5條sql語句。其中兩條是用於向連接表中插入記錄,從而建立Department和Employee之間的關聯關系。
      
          2、雙向1-N關聯
          對於1-N關聯,Hibernate推薦使用雙向關聯,而且不要讓1端控制關聯關系,而使用N的一端控制關聯關系。
雙向的1-N關聯,兩端都需要增加對關聯屬性的訪問,N的一端增加引用到關聯實體的屬性,1的一端增加集合屬性,集合元素為關聯實體。
          兩個持久化類和上面差不多,隻需要在Employee中增加對Department的引用屬性即可。
          2.1無連接表的雙向1-N關聯
          對於無連接表的雙向1-N關聯。N的一端需要增加<many-to-one…/>元素來映射關聯屬性,而1的一端需要使用<set…/>或者<bag…/>元素來映射關聯屬性。同時<set…/>或者<bag../>元素需要增加<key…/>子元素映射外鍵列,並且使用<one-to-many…/>子元素映射關聯屬性。
          在前面已經提到對於1-N關聯映射,通常不提倡1的一端控制關聯關系,而應該由N的一端來控制關聯關系。此時我們可以再<set…/>元素中指定inverse="true",用於指定1的一端不控制關聯關系
          Department映射文件:Department.hbm.xml
[html] 
<hibernate-mapping package="com.hibernate.domain" > 
    <class name="Department" table="department"> 
        <id name="id" column="departmentID"> 
             <generator class="native" /> 
        </id> 
        <property name="name" column="departmentName" /> 
        <!– 映射集合屬合 –> 
        <set name="employees" inverse="true" > 
            <!– 指定關聯的外鍵列 –> 
            <key column="departmentID" /> 
            <!– 用以映射到關聯類屬性 –> 
            <one-to-many class="Employee"/> 
        </set>     
    </class> 
</hibernate-mapping> 

          Employee映射文件:Employee.hbm.xml
[html] view plaincopyprint?
<hibernate-mapping package="com.hibernate.domain"> 
    <class name="Employee" table="employee"> 
        <id name="id" column="employeeID"> 
            <generator class="native" /> 
        </id> 
        <property name="name" column="employeeName" /> 
        <!– 用於映射N-1關聯實體,指定關聯實體類為 :Department,指定外鍵為:departmentID–> 
        <many-to-one name="department" column="departmentID" />     
    </class> 
</hibernate-mapping> 

          下面的程序段用於保存一個Department對象和兩個Employee對象
[java] 
static void add(){ 
    Session session = HibernateUtil.getSession(); 
    Transaction tx = session.beginTransaction(); 
     
    Department department = new Department(); 
    department.setName("國防部"); 
     
    //建立兩個對象 
    Employee employee1 = new Employee(); 
    employee1.setName("chentmt1"); 
    employee1.setDepartment(department);      //建立兩個對象的關聯關系 
     
    Employee employee2 = new Employee(); 
    employee2.setName("chentmt2"); 
    employee2.setDepartment(department);      //建立兩個對象的關聯關系 
      
    session.save(department);          //….1 
    session.save(employee2); 
    session.save(employee1); 
    tx.commit(); 
    session.close(); 

          SQL語句:
[sql] 
Hibernate: insert into department (departmentName) values (?) 
 
Hibernate: insert into employee (employeeName, departmentID) values (?, ?) 
 
Hibernate: insert into employee (employeeName, departmentID) values (?, ?) 

          通過上面的SQL語句可以看出,Hibernate並不是采用哪種先insert後update的方式來插入employee記錄的。而是通過一條insert SQL語句來執行的。為什麼?因為程序持久化Employee實體之前,Employee已經知道它所關聯Department實體(employee2.setDepartment(department);)。 所以為瞭保證比較好的性能,需要註意以下兩個問題:
          1、應該先持久化主表對象:Department。因為我們希望程序在持久化從表:Employee對象時,Hibernate可以為他的外鍵屬性值分配值。
          2、先設置兩個持久化類(Department和Employee)的關系,再保存持久化從表對象(Employee)。如果順序反過來,程序持久化Employee對象時,該對象還沒有關聯實體,所以Hibernate不能為對應記錄的外鍵列指定值,等到設置關聯關系時,Hibernate隻能再次使用update語句來修改瞭。
         
          2.2有連接表的雙向1-N關聯
          有連接表的雙向1-N關聯。1的一端使用集合元素映射,然後在集合元素中增加<many-to-many../>子元素,該子元素映射到關聯類。為保證該實體是1的一端,需要增加unique="true"屬性。N的一端則使用<join…/>元素來強制使用連接表。
          在N的一端使用<join../>元素強制使用連接表,因此將使用<key…/>子元素來映射連接表中外鍵列,器且使用<many-to-one../>來映射連接表中關聯實體的外鍵列;反過來,1的一端也將在<set…/>元素中使用<key…/>和<many-to-many…/>兩個子元素,他們也映射到連接表的兩列。為瞭保證得到正確的映射關系,應該滿足如下關系:<join…/>裡<key…/>子元素的column屬性和<set…/>裡<man-to-many…/>元素的column屬性相同;<join…/>裡<many-to-many…/>子元素的column屬性和<set../>元素的<key../>子元素的column屬性應該相等。
          下面就是Department和Employee兩個持久化類的映射文件:
          Department.hbm.xml
[html] 
<hibernate-mapping package="com.hibernate.domain" > 
    <class name="Department" table="department"> 
        <id name="id" column="departmentID"> 
            <generator class="native" /> 
        </id> 
        <property name="name" column="departmentName" /> 
        <set name="employees" inverse="true" table="department_employee"> 
            <!– 指定關聯的外鍵列 –> 
            <key column="departmentID" /> 
            <!– 用以映射到關聯類屬性 –> 
            <many-to-many class="Employee" column="employeeID" unique="true"/> 
        </set> 
         
    </class> 
</hibernate-mapping> 
 
          Employee.hbm.xml
[html] 
<hibernate-mapping package="com.hibernate.domain"> 
    <class name="Employee" table="employee"> 
        <id name="id" column="employeeID"> 
            <generator class="native" /> 
        </id> 
        <property name="name" column="employeeName" /> 
         
        <!– 使用join元素強制使用連接表 –> 
        <join table="department_employee"> 
            <!– key映射外鍵列 –> 
            <key column="employeeID" /> 
            <!– 映射關聯實體 –> 
            <many-to-one name="department" column="departmentID" /> 
        </join>   
    </class> 
</hibernate-mapping> 

作者:chenssy

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *