由有本人剛學習 Hibernate 不就,不是很明白這里面的道理,故先記錄下:
先將這個示例說說:
create table category (
catid char(10) not null,
name varchar(80) null,
descn varchar(255) null,
constraint pk_category primary key (catid)
) type=InnoDB;
create table product (
productid char(10) not null,
category char(10) not null,
name varchar(80) null,
descn varchar(255) null,
constraint pk_product primary key (productid),
constraint fk_product_1 foreign key (category) references category (catid)
) type=InnoDB;
Category 和 Product 的關(guān)系是一對多的關(guān)系。
Category.java 和 Product.java 就是 POJO。
Category.hbm.xml:
<class name="Category" table="category">
<id name="catid" column="catid" type="java.lang.String" >
<generator class="assigned"/>
</id>
<property name="name" column="name" type="java.lang.String" />
<property name="descn" column="descn" type="java.lang.String" />
<set name="products" table="product" inverse="true" cascade="all">
<key column="category"/>
<one-to-many class="Product"/>
</set>
</class>
Product.hbm.xml:
<class name="Product" table="product">
<id name="productid" column="productid" type="java.lang.String" >
<generator class="assigned"/>
</id>
<property name="name" column="name" type="java.lang.String" />
<property name="descn" column="descn" type="java.lang.String" />
<many-to-one name="category"
column="category"
class="Category"/>
</class>
測試的類:
protected void setUp() throws Exception {
sessionFactory = new Configuration().configure().buildSessionFactory();
session = sessionFactory.openSession();
}
protected void tearDown() throws Exception {
session.close();
sessionFactory.close();
}
public void testSave() throws Exception {
Category cat = new Category();
cat.setCatid("FISH");
cat.setName("Fish");
cat.setDescn("<image src=\"../images/fish_icon.gif\"><font size=\"5\" color=\"blue\"> Fish</font>");
Product pro = new Product();
pro.setProductid("K9-BD-01");
pro.setName("Bulldog");
pro.setDescn("<image src=\"../images/dog2.gif\">Friendly dog from England");
pro.setCategory(cat);
cat.getProducts().add(pro);
Transaction tx= session.beginTransaction();
session.save(cat);
tx.commit();
}
結(jié)果就出現(xiàn)摘要中所說的錯誤:
Hibernate: insert into category (name, descn, catid) values (?, ?, ?)
Hibernate: update product set name=?, descn=?, category=? where productid=?
10:07:08,062 ERROR SessionImpl:2399 - Could not synchronize database state with session
但將兩個 *.hbm.xml 文件中的
<id name="catid" column="catid" type="java.lang.String"> 改為
<id name="catid" column="catid" type="java.lang.String" unsaved-value="any">
<id name="productid" column="productid" type="java.lang.String"> 改為
<id name="productid" column="productid" type="java.lang.String" unsaved-value="any">
測試正常。
從夏昕的 Hibernate 開發(fā)指南中可以看到他介紹的 “關(guān)于unsaved-value”:
在非顯示數(shù)據(jù)保存時,Hibernate將根據(jù)這個值來判斷對象是否需要保存。
所謂顯式保存,是指代碼中明確調(diào)用session 的save、update、saveOrupdate 方法對對象進行持久化。如:session.save(user);
而在某些情況下,如映射關(guān)系中,Hibernate 根據(jù)級聯(lián)(Cascade)關(guān)系對聯(lián)接類進行保存。此時代碼中沒有針對級聯(lián)對象的顯示保存語句,需要Hibernate 根據(jù)對象當前狀態(tài)判斷是否需要保存到數(shù)據(jù)庫。此時,Hibernate即將根據(jù)unsaved-value進行判定。
首先Hibernate會取出目標對象的id。
之后,將此值與unsaved-value進行比對,如果相等,則認為目標對象尚未保存,否則,認為對象已經(jīng)保存,無需再進行保存操作。如:user對象是之前由hibernate從數(shù)據(jù)庫中獲取,同時,此user對象的若干個關(guān)聯(lián)對象address 也被加載,此時我們向user 對象新增一個address 對象,此時調(diào)用 session.save(user),hibernate會根據(jù)unsaved-value判斷user對象的數(shù)個address 關(guān)聯(lián)對象中,哪些需要執(zhí)行save操作,而哪些不需要。
對于我們新加入的address 對象而言,由于其id(Integer 型)尚未賦值,因此為null,與我們設(shè)定的unsaved-value(null)相同,因此hibernate將其視為一個未保存對象,將為其生成insert語句并執(zhí)行。這里可能會產(chǎn)生一個疑問,如果“原有”關(guān)聯(lián)對象發(fā)生變動(如user的某個“原有”的address對象的屬性發(fā)生了變化,所謂“原有”即此address對象已經(jīng)與user相關(guān)聯(lián),而不是我們在此過程中為之新增的),此時id值是從數(shù)據(jù)庫中讀出,并沒有發(fā)生改變,自然與unsaved-value(null)也不一樣,那么Hibernate是不是就不保存了?
上面關(guān)于PO、VO 的討論中曾經(jīng)涉及到數(shù)據(jù)保存的問題,實際上,這里的“保存”,實際上是“insert”的概念,只是針對新關(guān)聯(lián)對象的加入,而非數(shù)據(jù)庫中原有關(guān)聯(lián)對象的“update”。所謂新關(guān)聯(lián)對象,一般情況下可以理解為未與Session 發(fā)生關(guān)聯(lián)的VO。而“原有”關(guān)聯(lián)對象,則是PO。如上面關(guān)于PO、VO的討論中所述:對于save操作而言,如果對象已經(jīng)與Session相關(guān)聯(lián)(即已經(jīng)被加入Session的實體容器中),則無需進行具體的操作。因為之后的Session.flush過程中,Hibernate 會對此實體容器中的對象進行遍歷,查找出發(fā)生變化的實體,生成并執(zhí)行相應(yīng)的 update 語句。
針對上面的例子,當沒有設(shè)定 unsaved-value="any" 時,也就相當于 unsaved-value="none",不論主鍵屬性為任何值,都不可能為 none,因此 Hibernate 總是對 product 對象發(fā)送update(product);unsaved-value="any" 的時候,由于不論主鍵屬性為任何值,都肯定為 any,因此 Hibernate 總是對 product 對象發(fā)送 save(product)。
所以 Hibernate.org.cn 論壇中的 Robbin 建議在系統(tǒng)設(shè)計的時候,遵循如下原則:
1、使用Hibernate的id generator來生成無業(yè)務(wù)意義的主鍵,不使用有業(yè)務(wù)含義的字段做主鍵,不使用assigned。
2、使用對象類型(String/Integer/Long/...)來做主鍵,而不使用基礎(chǔ)類型(int/long/...)做主鍵
3、不使用composite-id來處理復合主鍵的情況,而使用UserType來處理該種情況。
那么你永遠用的是unsaved-value="null" ,不可能用到any/none/..了。