如上圖,是一個銷售系統的資料庫關聯圖,這個關聯圖說明 了以下關係:
- 一個銷售人員會有許多客戶,每個客戶則會歸屬到一個銷售人員下進行服務。
- 每個客戶會購買一到多個商品,同時一個商品也會賣給一到多個客戶。
上面兩種關係,第 1 種為一對多,第 2 種為多對多,底下先說明一對多的情況下,Hibernate 要怎麼處理? 會有兩種狀況,分別以 SALES 和 CUSTOMER 為主動,說明如下:
根據上面的關聯圖,建立 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);
從上面的說明應該可以發現一個明顯的現象,就是主動方要負責定義彼此的關聯,被動方只要在類別及設定檔中,依 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