構造這樣一個例子,在測試過程中來說明一些Hibernate的高級配置及其相關機制:
有三個類:Category.java,Prodcuct.java,ConfigurationTest.java,其中第三個類是用來測試的。
Category.java代碼:
package unsaved_value;
import ......
public class Category {
private Integer id;
private String name;
private String description;
private Set products;
public Category(){
id=null;
name=null;
description=null;
products=new HashSet ();
}
public void addProduct(Product p){
products.add(p);
}
//**********setter and getter
........
}
Product.java代碼:
package unsaved_value;
public class Product {
private Integer id;
private String name;
private Category category;
private String description;
public Product(){
}
//*******getter and setter
.........
}
ConfigurationTest.java
public void testSave()throws Exception{
Category category=new Category();
category.setName("java編程書籍2");
category.setDescription("編程經典書籍2");
Product pro=new Product();
pro.setName("java編程思想2");
pro.setDescription("第四版中文版2");
pro.setCategory(category);
category.addProduct(pro);
Transaction tx=session.beginTransaction();
assert (session!=null):("session is null");
session.save(category);
tx.commit();
}
Category代表產品目錄,而Product代表產品,顯然Category與Product是一對多的關系。Hibernate在映射一對多關系時,有兩種方式,一種是單向一對多,一種是雙向關系。兩者相比,雙向一對多的好處體現在兩方面:首先,也是很明顯的一點,由于是雙向關聯,我們在實際業務邏輯時將更方便,例如我們可以檢索一個Category下的所有Product,同時還可以檢索出Product屬于哪個。其次,雙向關系相對單向關系而言,在數據庫的訪問方面更有優勢。這一點留在后面講inverse時講
。雙向關聯比單向關聯唯一的”劣勢“,就在于雙向關聯需要比單向關聯多寫一個映射文件,這不問題。使用雙向關聯實現這兩個類同數據庫的映射:
Category.hbm.xml:
version="1.0" encoding="UTF-8"?>
"
<hibernate-mapping package="unsaved_value">
<class name="Category" table="category">
<id name="id" column="id">
<generator class="native">generator>
id>
<property name="name" column="name"/>
<property name="description" column="description"/>
<set name="products" table="product" lazy="true" inverse="true" cascade="all">
<key column="category"/>
<one-to-many class="Product"/>
set>
class>
hibernate-mapping>
Product.hbm.xml:
version="1.0" encoding="UTF-8"?>
"
<hibernate-mapping package="unsaved_value">
<class name="Product" table="product">
<id name="id" column="id" unsaved-value="null">
<generator class="native">generator>
id>
<property name="name" column="name"/>
<property name="description" column="description"/>
<many-to-one name="category"
column="category"
class="Category"
/>
class>
hibernate-mapping>
現在把這個例子所牽涉到的知識一一展開:
一.inverse
該詞的譯意是“反轉”,反轉什么——反轉控制端,這項配置決定了由關聯雙方中的哪一方來維持關聯關系(在數據庫中表現為外鍵約束)。上述配置中,在Category.hbm.xml中將inverse設置為true,意思是說“我需要反轉(控制端)”,反轉的結果是由對方即Product來維持關聯關系。用單向關聯更容易說明”維持關聯關系“是什么意思:考慮用單向關系來實現這個映射關系的情況,即由Category關聯到Product,考慮下面的代碼:
Product p=new Product();
..setXXX
Category c=new Category();
..設置Category的屬性
c.addProduct(p);//建立起了c和p的關聯關系
session.save(c);
會執行三條SQL語句:兩條插入語句,分別插入c和p,然后還有一條update語句建立起c和p的關聯(更新p的外鍵)。上面,我們說由Category端控制關聯,因此p.setCategory(c)這樣一句話是沒用的,它并不會導致在插入p的時候就設置p的外鍵以建立起兩者的關聯關系,從而節省一條update語句。同時我們還會看到,如果在數據庫模式中將p的外鍵設置成非空,這些代碼將不能執行,因為在插入p時,由于c和p的關聯關系還未建立起來,因此p的外鍵為空。回到雙向關聯上來,為了更清楚地明白inverse在雙向關聯中到底起什么作用,我們分別將其值設為true和false,看看打印出的的SQL有何區別:
inverse=true時的打印結果:
Hibernate: insert into category (name, description) values (?, ?)
Hibernate: insert into product (name, description, category) values (?, ?, ?)
inverse=false時的打印結果:
Hibernate: insert into category (name, description) values (?, ?)
Hibernate: insert into product (name, description, category) values (?, ?, ?)
Hibernate: update product set category=? where id=?
為什么inverse=true時會比inverse=false時少執行一條SQL語句?這是由控制端的不同造成的。前者說"我要反轉控制,由Product來控制關聯",因此在將p對象insert時,p已經設置了其category字段,從而建立了關聯關系,而后者說"我不反轉控制,由我自己來控制關聯",因此在將p對象insert后,c為了維持兩者的關聯,還要去執行一次update,以更新p的外鍵,從而建立起兩者的關聯關系。
結論:對于一對多雙向關系,始終在“一”那一方將其inverse設置成true,這樣會提高性能。
二.cascade
級聯。當關聯的"一"方進行某種動作(更新,刪除)時,"多"方即使沒有顯式地進行編碼,它也會自動進行同樣的動作。cascade的可選值有:
all : 所有情況下均進行關聯操作。即是save-update + delete
none:所有情況下均不進行關聯操作。這是默認值。
save-update:在執行save/update/saveOrUpdate時進行關聯操作。
delete:在執行delete時進行關聯操作。
all-delete-orphan:A:級聯save-update B級聯delete C:刪除所有孤兒項(orphan孤兒)。先看看父子關系,例如在Customer和Order的模型中,這兩者便是父子關系,當一個Customer的生命周期決定Order的生命周期,如果一個Customer不在了,其相關的Order繼續存在是毫無業務意義的。刪除所有孤兒項的意思即是,刪除所有與父對象失去關聯關系的子對象。
三.lazy
是否延遲加載。一般來說,應該延遲加載,即將lazy設為true。延遲加載的相關點很多,這在另外的學習筆記中總結。
四.unsaved-value
以上是"一"方的重要配置,再看看"多"方的一個重要配置:unsaved-value,就像上面Product.hbm.xml中的設置那樣,這一項在id的配置中設置。這一設置是與級聯一起工作的。關于這一點,robbin講的很清楚:
當你顯式的使用session.save()或者session.update()操作一個對象的時候,實際上是用不到unsaved-value 的。某些情況下(父子表關聯保存),當你在程序中并沒有顯式的使用save或者update一個持久對象,那么Hibernate需要判斷被操作的對象究竟是一個已經持久化過的持久對象,是一個尚未被持久化過的內存臨時對象。例如:
Session session = ...;
Transaction tx = ...;
Parent parent = (Parent) session.load(Parent.class, id);
Child child = new Child();
child.setParent(parent);
child.setName("sun");
parent.addChild(child);
s.update(parent);
s.flush();
tx.commit();
s.close();
在上例中,程序并沒有顯式的session.save(child); 那么Hibernate需要知道child究竟是一個臨時對象,還是已經在數據庫中有的持久對象。如果child是一個新創建的臨時對象(本例中就是這種情況),那么Hibernate應該自動產生session.save(child)這樣的操作,如果child是已經在數據庫中有的持久對象,那么 Hibernate應該自動產生session.update(child)這樣的操作。因此我們需要暗示一下Hibernate,究竟 child對象應該對它自動save還是update。在上例中,顯然我們應該暗示Hibernate對child自動save,而不是自動 update。那么Hibernate如何判斷究竟對child是save還是update呢?它會取一下child的主鍵屬性 child.getId() ,這里假設id是 java.lang.Integer類型的。如果取到的Id值和hbm映射文件中指定的unsave-value相等,那么Hibernate認為 child是新的內存臨時對象,發送save,如果不相等,那么Hibernate認為child是已經持久過的對象,發送update。unsaved-value="null" (默認情況,適用于大多數對象類型主鍵 Integer/Long/String/...)
當Hibernate取一下child的Id,取出來的是null(在上例中肯定取出來的是null),和unsaved-value設定值相等,發送save(child)
當Hibernate取一下child的id,取出來的不是null,那么和unsaved-value設定值不相等,發送update(child)
unsaved-value的可選配置有:
none,any,null
unsaved-value="none"和unsaved-value="any"主要用在主鍵屬性不是通過Hibernate生成,而是程序自己setId()的時候。unsaved-value="none"和unsaved-value="any"究竟有什么含義了。如果你非要用assigned不可,那么繼續解釋一下:
unsaved-value="none" 的時候,由于不論主鍵屬性為任何值,都不可能為none,因此Hibernate總是對child對象發送update(child)
unsaved-value="any" 的時候,由于不論主鍵屬性為任何值,都肯定為any,因此Hibernate總是對child對象發送save(child)
大多數情況下,可以避免使用assigned,只有當你使用復合主鍵的時候不得不手工setId(),這時候需要你自己考慮究竟怎么設置unsaved-value了,根據你自己的需要來定。
關于為什么不要使主鍵帶有義務意義,robbin的解釋很清楚:還是以上面的例子打比方,如果我們將Category的某一個性質(比如產品序號或者名稱)作為主鍵,如果后來由于業務需要,我們把這個性質改了,那將不可僻免地要去修改與這個對象相關聯的所有數據的外鍵,而如果我們只要代理主鍵,這個問題就可完全僻免。