構(gòu)造這樣一個例子,在測試過程中來說明一些Hibernate的高級配置及其相關(guān)機制:
有三個類: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("編程經(jīng)典書籍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代表產(chǎn)品目錄,而Product代表產(chǎn)品,顯然Category與Product是一對多的關(guān)系。Hibernate在映射一對多關(guān)系時,有兩種方式,一種是單向一對多,一種是雙向關(guān)系。兩者相比,雙向一對多的好處體現(xiàn)在兩方面:首先,也是很明顯的一點,由于是雙向關(guān)聯(lián),我們在實際業(yè)務(wù)邏輯時將更方便,例如我們可以檢索一個Category下的所有Product,同時還可以檢索出Product屬于哪個。其次,雙向關(guān)系相對單向關(guān)系而言,在數(shù)據(jù)庫的訪問方面更有優(yōu)勢。這一點留在后面講inverse時講
。雙向關(guān)聯(lián)比單向關(guān)聯(lián)唯一的”劣勢“,就在于雙向關(guān)聯(lián)需要比單向關(guān)聯(lián)多寫一個映射文件,這不問題。使用雙向關(guān)聯(lián)實現(xiàn)這兩個類同數(shù)據(jù)庫的映射:
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>
現(xiàn)在把這個例子所牽涉到的知識一一展開:
一.inverse
該詞的譯意是“反轉(zhuǎn)”,反轉(zhuǎn)什么——反轉(zhuǎn)控制端,這項配置決定了由關(guān)聯(lián)雙方中的哪一方來維持關(guān)聯(lián)關(guān)系(在數(shù)據(jù)庫中表現(xiàn)為外鍵約束)。上述配置中,在Category.hbm.xml中將inverse設(shè)置為true,意思是說“我需要反轉(zhuǎn)(控制端)”,反轉(zhuǎn)的結(jié)果是由對方即Product來維持關(guān)聯(lián)關(guān)系。用單向關(guān)聯(lián)更容易說明”維持關(guān)聯(lián)關(guān)系“是什么意思:考慮用單向關(guān)系來實現(xiàn)這個映射關(guān)系的情況,即由Category關(guān)聯(lián)到Product,考慮下面的代碼:
Product p=new Product();
..setXXX
Category c=new Category();
..設(shè)置Category的屬性
c.addProduct(p);//建立起了c和p的關(guān)聯(lián)關(guān)系
session.save(c);
會執(zhí)行三條SQL語句:兩條插入語句,分別插入c和p,然后還有一條update語句建立起c和p的關(guān)聯(lián)(更新p的外鍵)。上面,我們說由Category端控制關(guān)聯(lián),因此p.setCategory(c)這樣一句話是沒用的,它并不會導(dǎo)致在插入p的時候就設(shè)置p的外鍵以建立起兩者的關(guān)聯(lián)關(guān)系,從而節(jié)省一條update語句。同時我們還會看到,如果在數(shù)據(jù)庫模式中將p的外鍵設(shè)置成非空,這些代碼將不能執(zhí)行,因為在插入p時,由于c和p的關(guān)聯(lián)關(guān)系還未建立起來,因此p的外鍵為空。回到雙向關(guān)聯(lián)上來,為了更清楚地明白inverse在雙向關(guān)聯(lián)中到底起什么作用,我們分別將其值設(shè)為true和false,看看打印出的的SQL有何區(qū)別:
inverse=true時的打印結(jié)果:
Hibernate: insert into category (name, description) values (?, ?)
Hibernate: insert into product (name, description, category) values (?, ?, ?)
inverse=false時的打印結(jié)果:
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時少執(zhí)行一條SQL語句?這是由控制端的不同造成的。前者說"我要反轉(zhuǎn)控制,由Product來控制關(guān)聯(lián)",因此在將p對象insert時,p已經(jīng)設(shè)置了其category字段,從而建立了關(guān)聯(lián)關(guān)系,而后者說"我不反轉(zhuǎn)控制,由我自己來控制關(guān)聯(lián)",因此在將p對象insert后,c為了維持兩者的關(guān)聯(lián),還要去執(zhí)行一次update,以更新p的外鍵,從而建立起兩者的關(guān)聯(lián)關(guān)系。
結(jié)論:對于一對多雙向關(guān)系,始終在“一”那一方將其inverse設(shè)置成true,這樣會提高性能。
二.cascade
級聯(lián)。當(dāng)關(guān)聯(lián)的"一"方進(jìn)行某種動作(更新,刪除)時,"多"方即使沒有顯式地進(jìn)行編碼,它也會自動進(jìn)行同樣的動作。cascade的可選值有:
all : 所有情況下均進(jìn)行關(guān)聯(lián)操作。即是save-update + delete
none:所有情況下均不進(jìn)行關(guān)聯(lián)操作。這是默認(rèn)值。
save-update:在執(zhí)行save/update/saveOrUpdate時進(jìn)行關(guān)聯(lián)操作。
delete:在執(zhí)行delete時進(jìn)行關(guān)聯(lián)操作。
all-delete-orphan:A:級聯(lián)save-update B級聯(lián)delete C:刪除所有孤兒項(orphan孤兒)。先看看父子關(guān)系,例如在Customer和Order的模型中,這兩者便是父子關(guān)系,當(dāng)一個Customer的生命周期決定Order的生命周期,如果一個Customer不在了,其相關(guān)的Order繼續(xù)存在是毫無業(yè)務(wù)意義的。刪除所有孤兒項的意思即是,刪除所有與父對象失去關(guān)聯(lián)關(guān)系的子對象。
三.lazy
是否延遲加載。一般來說,應(yīng)該延遲加載,即將lazy設(shè)為true。延遲加載的相關(guān)點很多,這在另外的學(xué)習(xí)筆記中總結(jié)。
四.unsaved-value
以上是"一"方的重要配置,再看看"多"方的一個重要配置:unsaved-value,就像上面Product.hbm.xml中的設(shè)置那樣,這一項在id的配置中設(shè)置。這一設(shè)置是與級聯(lián)一起工作的。關(guān)于這一點,robbin講的很清楚:
當(dāng)你顯式的使用session.save()或者session.update()操作一個對象的時候,實際上是用不到unsaved-value 的。某些情況下(父子表關(guān)聯(lián)保存),當(dāng)你在程序中并沒有顯式的使用save或者update一個持久對象,那么Hibernate需要判斷被操作的對象究竟是一個已經(jīng)持久化過的持久對象,是一個尚未被持久化過的內(nèi)存臨時對象。例如:
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究竟是一個臨時對象,還是已經(jīng)在數(shù)據(jù)庫中有的持久對象。如果child是一個新創(chuàng)建的臨時對象(本例中就是這種情況),那么Hibernate應(yīng)該自動產(chǎn)生session.save(child)這樣的操作,如果child是已經(jīng)在數(shù)據(jù)庫中有的持久對象,那么 Hibernate應(yīng)該自動產(chǎn)生session.update(child)這樣的操作。因此我們需要暗示一下Hibernate,究竟 child對象應(yīng)該對它自動save還是update。在上例中,顯然我們應(yīng)該暗示Hibernate對child自動save,而不是自動 update。那么Hibernate如何判斷究竟對child是save還是update呢?它會取一下child的主鍵屬性 child.getId() ,這里假設(shè)id是 java.lang.Integer類型的。如果取到的Id值和hbm映射文件中指定的unsave-value相等,那么Hibernate認(rèn)為 child是新的內(nèi)存臨時對象,發(fā)送save,如果不相等,那么Hibernate認(rèn)為child是已經(jīng)持久過的對象,發(fā)送update。unsaved-value="null" (默認(rèn)情況,適用于大多數(shù)對象類型主鍵 Integer/Long/String/...)
當(dāng)Hibernate取一下child的Id,取出來的是null(在上例中肯定取出來的是null),和unsaved-value設(shè)定值相等,發(fā)送save(child)
當(dāng)Hibernate取一下child的id,取出來的不是null,那么和unsaved-value設(shè)定值不相等,發(fā)送update(child)
unsaved-value的可選配置有:
none,any,null
unsaved-value="none"和unsaved-value="any"主要用在主鍵屬性不是通過Hibernate生成,而是程序自己setId()的時候。unsaved-value="none"和unsaved-value="any"究竟有什么含義了。如果你非要用assigned不可,那么繼續(xù)解釋一下:
unsaved-value="none" 的時候,由于不論主鍵屬性為任何值,都不可能為none,因此Hibernate總是對child對象發(fā)送update(child)
unsaved-value="any" 的時候,由于不論主鍵屬性為任何值,都肯定為any,因此Hibernate總是對child對象發(fā)送save(child)
大多數(shù)情況下,可以避免使用assigned,只有當(dāng)你使用復(fù)合主鍵的時候不得不手工setId(),這時候需要你自己考慮究竟怎么設(shè)置unsaved-value了,根據(jù)你自己的需要來定。
關(guān)于為什么不要使主鍵帶有義務(wù)意義,robbin的解釋很清楚:還是以上面的例子打比方,如果我們將Category的某一個性質(zhì)(比如產(chǎn)品序號或者名稱)作為主鍵,如果后來由于業(yè)務(wù)需要,我們把這個性質(zhì)改了,那將不可僻免地要去修改與這個對象相關(guān)聯(lián)的所有數(shù)據(jù)的外鍵,而如果我們只要代理主鍵,這個問題就可完全僻免。