Google Code Prettify

2016年2月9日 星期二

Hibernate: one-to-many (xml)


如上圖,是一個銷售系統的資料庫關聯圖,這個關聯圖說明 了以下關係:
  1. 一個銷售人員會有許多客戶,每個客戶則會歸屬到一個銷售人員下進行服務。
  2. 每個客戶會購買一到多個商品,同時一個商品也會賣給一到多個客戶。
上面兩種關係,第 1 種為一對多,第 2 種為多對多,底下先說明一對多的情況下,Hibernate 要怎麼處理? 會有兩種狀況,分別以 SALES 和 CUSTOMER 為主動,說明如下:
  • SALES 主動 ("一"方主動)
根據上面的關聯圖,建立 SALES 和 CUSTOMER 兩個 table 的類別,如下,建構子的部份是為了後面的需求而加上去,與關聯圖無關,請先忽略,重點在於變數及其相對應的 getter & setter method。 Sales 類別的變數除了 table 中的兩個欄位各建立一個變數外,為了表示出和 Customer 的一對多關係,增加了 customer 這個變數,用來儲存所關聯到客戶,因為客戶個數會有多個,以 Set 來承載。
public class Sales implements Serializable {
    private String emNo;
    private String name;
    private Set<Customer> customer;
    
    public Sales() {}
    
    public Sales(String emNo, String name) {
        this.emNo = emNo;
        this.name = name;
    }
    
    public Sales(String emNo, String name, Set<Customer> customer) {
        this.emNo = emNo;
        this.name = name;
        this.customer = this.customer;
    }
    
    public String getEmNo() {
        return emNo;
    }
    public void setEmNo(String emNo) {
        this.emNo = emNo;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Collection<Customer> getCustomer() {
        return customer;
    }
    public void setCustomer(Set<Customer> customer) {
        this.customer = customer;
    }
}
Customer 類別在表現 table CUSTOMER 時,因為是被動的一方,只要依 table 上的欄位建立相關的變數,並給予 getter & setter method 就可以了。
public class Customer implements java.io.Serializable {
    private String cid;
    private String name;
    private String emNo;
    
    public Customer(String cid, String name) {
        this.cid = cid;
        this.name = name;
    }
    
    public Customer(String cid, String name, String emNo) {
        this.cid = cid;
        this.name = name;
        this.emNo = emNo;
    }
    
    public String getCid() {
        return cid;
    }
    public void setCid(String cid) {
        this.cid = cid;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    public String getEmNo() {
        return emNo;
    }

    public void setEmNo(String emNo) {
        this.emNo = emNo;
    }
}
Hibernate 進行 OR Mapping 的方式有兩種,分別是以 xml 或 annotation 來將類別與資料庫中的 table 連結,底下只說明 xml 的方式,一般來說,一個 table 除了會有一個相對應的類別外,也會有一個相對應的 xml 檔,檔名通常取為 "table".hbm.xml,在這裡就是取名為 Sales.hbm.xml 及 Customer.hbm.xml。
  • 注意看一下 hibernate-mapping 的屬性 package,那是 Java 類別所在的 package,這個屬性可以不寫,而在後面其它 tag 的 class 屬性中,寫出類別的全域名稱。
  •  set tag 要對應到 class SALES 中的 customer 的型別,除了用 Set 外,還有許多選擇,之後再說明。
  •  cascade 屬性有許多的選項,這裡寫 all 當然就是全部的選項都包括,選項有 save-update、delete、  delete-orphan,用來指定關聯的屬性。
  •  SALES 和 CUSTOMER 間的關係為一對屬,所以要加上 one-to-many 的 tag。 
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="idv.steven.orm.market">
    <class name="Sales" table="SALES">
        <id name="emNo" type="java.lang.String">
            <column name="EMNO" />
            <generator class="assigned" />
        </id>
        <property name="name" type="java.lang.String">
            <column name="NAME" />
        </property>
        
        <set name="customer" table="CUSTOMER" inverse="true" cascade="all">
            <key column="EMNO" not-null="true" />
            <one-to-many class="Customer" />
        </set>
    </class>
</hibernate-mapping>
  • CUSTOMER 依類別那樣,因為是被動的,也只是將 table 裡的欄位定義好就可以了。
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="idv.steven.orm.market">
    <class name="Customer" table="CUSTOMER">
        <id name="cid" type="java.lang.String">
            <column name="CID" />
            <generator class="assigned" />
        </id>
        <property name="name" type="java.lang.String">
            <column name="NAME" />
        </property>
        <property name="emNo" type="java.lang.String">
            <column name="EMNO" />
        </property>
    </class>
</hibernate-mapping>
類別寫了,xml 設定檔也搞定了,接下來看一下程式怎麼寫吧 ~
注意看一下程式,只有最後兩行將資料儲存到 table SALES,但是,實際查詢資料庫會發現,table CUSTOMER 裡也多了三筆資料,這就是上面的類別和 xml 設定為什麼要說定那些關聯的緣故,透過這些關聯,Hibernate 會將相關的資料一併處理 (儲存、更新或刪除)。
Sales s1 = new Sales("007528", "黃藥師");
Sales s2 = new Sales("001569", "江湖郎中");
        
Customer c1 = new Customer("1000000001", "張無忌", s1);
Customer c2 = new Customer("2000000025", "趙敏", s1);
Customer c3 = new Customer("2000000337", "周芷若", s2);
    
Set<Customer> sc1 = new HashSet<Customer>();
sc1.add(c1);
sc1.add(c2);
s1.setCustomer(sc1);
        
Set<Customer> sc2 = new HashSet<Customer>();
sc2.add(c3);
s2.setCustomer(sc2);

session.save(s1);
session.save(s2);




  • CUSTOMER 主動 ("多"方主動)
從上面的說明應該可以發現一個明顯的現象,就是主動方要負責定義彼此的關聯,被動方只要在類別及設定檔中,依 table layout 定義好相關的變數、設定就可以了。現在看一下"多"方主動時,怎麼定義類別吧!

如下,Sales 類別毫無懸念的依照 table layou 定義即可,要注意的是 Customer 類別,現在它是主動方,所以原本的 emNo 欄位拿掉,換成一個指向 Sales 類別的變數 -- sales,這就像是在建立 foreign key。
public class Sales implements Serializable {
    private String emNo;
    private String name;

public Sales(String emNo, String name) {
        this.emNo = emNo;
        this.name = name;
    }
        
    public String getEmNo() {
        return emNo;
    }
    public void setEmNo(String emNo) {
        this.emNo = emNo;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
public class Customer implements java.io.Serializable {
    private String cid;
    private String name;
    private Sales sales;
    
    public Customer(String cid, String name) {
        this.cid = cid;
        this.name = name;
    }
    
    public Customer(String cid, String name, Sales sales) {
        this.cid = cid;
        this.name = name;
        this.sales = sales;
    }
    
    public String getCid() {
        return cid;
    }
    public void setCid(String cid) {
        this.cid = cid;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    public Sales getSales() {
        return sales;
    }

    public void setSales(Sales sales) {
        this.sales = sales;
    }
}

接下來看 xml 設定檔 … Sales.hbm.xml 也是毫無懸念的,因為是被動端,完全依資料庫的定義設定。Customer.hbm.xml 中要注意的是如何定義多對一的關係? 這裡引入了 many-to-one 這個 tag,它的屬性前面基本上都有說明過,不再重複。
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="idv.steven.orm.market">
    <class name="Sales" table="SALES">
        <id name="emNo" type="java.lang.String">
            <column name="EMNO" />
            <generator class="assigned" />
        </id>
        <property name="name" type="java.lang.String">
            <column name="NAME" />
        </property>
    </class>
</hibernate-mapping>
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="idv.steven.orm.market">
    <class name="Customer" table="CUSTOMER">
        <id name="cid" type="java.lang.String">
            <column name="CID" />
            <generator class="assigned" />
        </id>
        <property name="name" type="java.lang.String">
            <column name="NAME" />
        </property>
        <many-to-one name="sales" class="Sales" column="EMNO" cascade="all" not-null="true" />
    </class>
</hibernate-mapping>
接下來當然就看程式怎麼寫囉 ~ 如下,注意看一下最後三行,現在變成儲存 customer,由 Hibernate 透過關聯幫我們儲存 sales 相關資料。
Sales s1 = new Sales("007528", "黃藥師");
Sales s2 = new Sales("001569", "江湖郎中");

Customer c1 = new Customer("1000000001", "張無忌", s1);
Customer c2 = new Customer("2000000025", "趙敏", s1);
Customer c3 = new Customer("2000000337", "周芷若", s2);
                
session.save(c1);
session.save(c2);
session.save(c3);

  • 雙向關聯

Hibernate 建議,在一對多的狀況下,最好是雙向關聯,且由多方主動。

** Source Code:  https://bitbucket.org/twleader59/orm

沒有留言:

張貼留言