Google Code Prettify

2016年2月11日 星期四

Hibernate: get、load、lazy 討論

繼續前兩篇的討論 ...


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

前兩篇討論的是 insert,對於許多 xml tag 的屬性也沒有多作說明,這一篇開始逐項的補充。(在開始說明前,先為每個 class 加上 toString(),以輸出欄位值。)

在之前 xml 設定中 set tag 有個屬性 lazy,通常我們會將它設定為 true,表示會延遲下載,那麼有沒有延遲下載有什麼差別? 看一下下面這段程式:
 1 Customer c = session.load(Customer.class, "2000000025");
 2 
 3 if (c == null) {
 4     System.out.println("customer is null");
 5 }
 6 else {
 7     System.out.println(c.toString());
 8     //System.out.println(c.getSales().toString());
 9     //System.out.println(c.getGoods().toString());
10 }
這段程式執行後,Hibernate 會如我們期待的產生以下 sql,由 CUSTOMER table 中取出我們要的資料。
(在 hibernate.cfg.xml 中加入 <property name="show_sql">true</property> 即可輸出 sql)
Hibernate: 
    select
        customer0_.CID as CID1_0_0_,
        customer0_.NAME as NAME2_0_0_,
        customer0_.EMNO as EMNO3_0_0_ 
    from
        STEVEN.CUSTOMER customer0_ 
    where
        customer0_.CID=?
接著把 Customer.hbm.xml 中的 lazy 屬性設定為 false,再重新執行一次,這次不只 select CUSTOMER,還 select GOODS,Hibernate 預設 lazy 值為 true,如果設定為 false 不延遲載入,Hibernate 就會把相關的資料也一併查詢出來。資料少的時候這樣是很方便,不過,資料多的話,很可能會因為這些不一定需要的 select 動作,讓系統效能變得很差!
Hibernate: 
    select
        customer0_.CID as CID1_0_0_,
        customer0_.NAME as NAME2_0_0_,
        customer0_.EMNO as EMNO3_0_0_ 
    from
        STEVEN.CUSTOMER customer0_ 
    where
        customer0_.CID=?
Hibernate: 
    select
        goods0_.CID as CID1_2_0_,
        goods0_.GID as GID2_2_0_,
        goods1_.GID as GID1_1_1_,
        goods1_.NAME as NAME2_1_1_ 
    from
        STEVEN.ORDERS goods0_ 
    inner join
        STEVEN.GOODS goods1_ 
            on goods0_.GID=goods1_.GID 
    where
        goods0_.CID=?
接下來把測試程式的第 8、9 行的註解 // 拿掉,同時輸出三項看看會有什麼結果? 結果不管 lazy 是 true 或 false,CUSTOMER、SALES、ORDERS 三個 table 都會 select 一次,如下,當然啦~ 因為程式本來就是三個資料都需要,不過,lazy 為 true 和 false 時,產生 sql 的時機還是會不同,lazy 為 true 時是真的需要時才 select,為 false 時則會先 select 出相關的資料,強烈建議,沒有特別的需求的話,lazy 都應該是設定為 true。
Hibernate: 
    select
        customer0_.CID as CID1_0_0_,
        customer0_.NAME as NAME2_0_0_,
        customer0_.EMNO as EMNO3_0_0_ 
    from
        STEVEN.CUSTOMER customer0_ 
    where
        customer0_.CID=?
Hibernate: 
    select
        goods0_.CID as CID1_2_0_,
        goods0_.GID as GID2_2_0_,
        goods1_.GID as GID1_1_1_,
        goods1_.NAME as NAME2_1_1_ 
    from
        STEVEN.ORDERS goods0_ 
    inner join
        STEVEN.GOODS goods1_ 
            on goods0_.GID=goods1_.GID 
    where
        goods0_.CID=?
Hibernate: 
    select
        sales0_.EMNO as EMNO1_3_0_,
        sales0_.NAME as NAME2_3_0_ 
    from
        STEVEN.SALES sales0_ 
    where
        sales0_.EMNO=?
上面的程式是用 load 來查詢出資料,Hibernate 另外提供有 get 這個 method,用法都一樣,差別在於是否延遲下載! 使用 get 時,Hibernate 是立即的到資料庫查詢,而使用 load 則可能會延遲到真的需要使用時才查詢。



Oracle 亂碼1/2

在 Oracle 中明明 insert 的是中文,select 出來卻是亂碼,為什麼? 通常的是因 client 端和 server 端的編碼不一致造成的! 解決的辦法很簡單:
  1. 在 client 和 server 都執行如下指令
  2. select userenv('language') from dual;
    這樣可以查出編碼 (charset),得到的結果可能如下:
     TRADITIONAL CHINESE_TAIWAN.AL32UTF8
    重點在後面的編碼 (紅色部份),確認兩邊不一致,那麼就確定是編碼不一致造成亂碼的!
  3. 在 client 新增一個環境變數 NLS_LANG,值設定為 server 端查到的 charset,例如:
  4. NLS_LANG = AL32UTF8
    重啟 client 端的軟體 (或許是 toad or sql developer),再查詢一次,應該已經可以正常顯示。

2016年2月10日 星期三

Database

項目日期
* Install / Configure / Concept
1第一次使用 MySQL
2014/10/10
2Oracle 亂碼1/2
2016/02/11
3install Redis
2018/07/21
4Redis 的七種資料型別
2018/07/24
5Redis: master / slave
2018/09/08
6install Apache Spark (CentOS 7.x)
2019/07/20
7install MS SQL Server@CentOS7
2021/02/12
* Programming - myBatis
1spring db connection (xml)
2015/12/22
2spring 4.x + struts 2.x + mybatis 3.x - getting started
2014/10/11
* Programming - Hibernate
1Hibernate: one-to-many (xml)
2016/02/09
2Hibernate: many-to-many (xml)
2016/02/10
3Hibernate: get、load、lazy 討論
2016/02/11
4Hibernate: 與 spring framework 整合
2016/03/06
5Hibernate + Spring + JPA (configure)
2016/03/20
* Programming - JPA + Spring
1JPA + Hibernate + Spring + JUnit (annotation)
2016/10/22
2JPA + Spring: 資料庫連線設定 (JNDI@Tomcat)
2017/01/21
3JPA + Spring: one-to-many (I)
2016/12/24
4JPA + Spring: many-to-many
2016/12/25
5JPA + Spring: 繼承 (@MappedSuperclass)
2017/01/02
6spring data: CrudRepository 自動生成
2017/01/08
7spring data: PagingAndSortingRepository
2017/01/28
8JPA + Spring: one-to-many (II)
2017/01/25
9JPA + Spring: 查詢結果為非實體 table (複合型別)
2017/01/11
10JPA: 新增含 SEQUENCE 欄位的資料
2016/04/19
11在 embedded tomcat 中使用 Tomcat 資料庫連線
2018/03/18
12Spring Data Redis
2018/07/23
* Programming - Python
1Python: ORM using Django
2018/10/14
2Python: ORM using Flask
2019/08/25
3Python: ORM (one-to-many)
2019/09/07
4Python: ORM (many-to-many)
2019/09/08
* Design
1報表結果要不要關連到組織主檔?
2015/05/28

Hibernate: many-to-many (xml)



上一篇完成了 one-to-many,這篇要說明多對多的狀況 (many-to-many),也就是第 2 個情節,客戶訂購商品,相關的 table 有 CUSTOMER、GOODS 及 ORDERS。
以物件的視角來看客戶和商品,我們了解彼此間是多對多的關係,即一個客戶可以購買多種商品,一種商品也可以賣給多個客戶,所以在程式中會定義 Customer、Goods 兩個類別,並於類別中指出彼此的關係。但是,在關聯式資料庫,多對多的關係必須引進第三個 table,在這裡就是 ORDERS,利用它來指出 CUSTOMER 和 GOODS 間的多對多關係,如上的 table layout,ORDERS 中每筆資料會有 CID 及 GID 欄位,指出這筆訂單是屬於那個客戶及購買了那一項商品。接下來看類別如何定義 …
底下的類別 Customer,我們延續上一篇的程式,再增加 goods 變數,用來指出一個客戶可以購買多項商品。在類別 Goods 中,同樣的也有個變數 customer,用來指出一項商品可以賣給多個客戶。table ORDERS 要給它一個類別嗎?? 答案是要! 簡單的給它一個 POJO 就可以了,hibernate 會用到!
public class Customer implements java.io.Serializable {
    private String cid;
    private String name;
    private Sales sales;
    
    private Set<Goods> goods = new HashSet<Goods>();
    
    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;
    }

    public Set<Goods> getGoods() {
        return goods;
    }

    public void setGoods(Set<Goods> goods) {
        this.goods = goods;
    }
    
    public void addGoods(Goods g) {
        goods.add(g);
    }
}
public class Goods {
    private String gid;
    private String name;
    
    private Set<Customer> customer = new HashSet<Customer>();
    
    public Goods(String gid, String name) {
        this.gid = gid;
        this.name = name;
    }
    
    public Goods(String gid, String name, Set<Customer> customer) {
        this.gid = gid;
        this.name = name;
        this.customer = customer;
    }
    
    public String getGid() {
        return gid;
    }
    public void setGid(String gid) {
        this.gid = gid;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Set<Customer> getCustomer() {
        return customer;
    }
    public void setCustomer(Set<Customer> customer) {
        this.customer = customer;
    }
    
    public void addCustomer(Customer c) {
        customer.add(c);
    }
}
public class Orders {
    private String cid;
    private String gid;
    
    public String getCid() {
        return cid;
    }
    public void setCid(String cid) {
        this.cid = cid;
    }
    public String getGid() {
        return gid;
    }
    public void setGid(String gid) {
        this.gid = gid;
    }
}
xml 設定檔如下,最重要的是綠色部份,在類別中只以商業需求的角度指出客戶和商品的關係,實際上怎麼儲存到資料庫? 這就要在設定檔中交代清楚,從下面的設定檔,可以看到 Customer 類別中的 goods 變數,實際上是透過 ORDERS 這個 table 為中介與 Goods 產生多對多的關係,Goods 類別中的 customer 變數也一樣,是透過 ORDERS 這個 table 為中介與 Customer 產生多對多關係。
<?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" />
        
        <set name="goods" table="ORDERS" inverse="false" lazy="true" cascade="all">
            <key>
                <column name="CID" not-null="true"/>
            </key>
            <many-to-many column="GID" class="Goods" />
        </set>
    </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="Goods" table="GOODS">
        <id name="gid" type="java.lang.String">
            <column name="GID" />
            <generator class="assigned" />
        </id>
        <property name="name" type="java.lang.String">
            <column name="NAME" />
        </property>
        
        <set name="customer" table="ORDERS" inverse="true" lazy="true">
            <key>
                <column name="GID" not-null="true" />
            </key>
            <many-to-many column="CID" class="Customer" />
        </set>
    </class>
</hibernate-mapping>
最後當然要看一下程式怎麼寫,如下:
Sales s3 = new Sales("008527", "百曉生");
Customer c4 = new Customer("5000005006", "未來事件預測商社", s3);
Set<Customer> customer = new HashSet<Customer>();
customer.add(c4);
Goods g1 = new Goods("AZ00000001", "陰陽和合散", customer);
c4.addGoods(g1);
        
session.save(c4);
執行結果應該可以看到,table SALES、CUSTOMER、GOODS、ORDERS 都有相關的資料。上面的程式是新的客戶購買商品,如果是既有客戶,程式要怎麼寫? 如下:
Customer c2 = session.get(Customer.class, "2000000025");

Goods g2 = new Goods("6Z00001001", "百花消魂散");
g2.addCustomer(c2);
c2.addGoods(g2);

session.save(c2);



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