2007-03-24

[Note] Hibernate Update or Merge

Hibernate物件的狀態有四種:transient、persistent、detached與removed(參考[Note] Hibernate Object States),這邊要講的是從detached到persistent這條路。

不明確的方法就不管了,指的就是transition persistent:A被persitent,導致A的關係人B也persistent,所以重點還是在怎麼讓A被persitent。

明確的方法有三種:update()、lock()與merge()。

update()
最熟悉的當就是update()了。
session.update(detachedObj);
但是有一點要特別注意的,就是這個動作一定會觸發一次sql update,即使detached obj沒有被修改,原因在obj是detached,所以Hibernate無從得知是否被修改過了,所以只好統統update。
可以設定select-before-update="true"要求Hibernate先去select一次舊資料來比對決定是否要update,有點囉唆,但好處有二:
  • 當obj很大時(就是column很多時,多少算多?五十個以上吧),可以避免無謂的update。
  • 當upadte會觸發Hibernate interceptor或db trigger時,導致不想要的副作用時。
lock()
有點麻煩的方式,因為有個參數要設定LockMode。
session.lock(detachedObj, LockMode.NONE);
  • LockMode.NONE:狀態從detached變成persistent,但是Hibernate不管這個detached obj有沒有被修改過,一律假設是乾淨未被修改過的,但是在lock之後的修改就會sync到db了,若lock之前有修改這時也會sync到db裡。
  • 其他的LockMode就會檢查detached obj有沒有被修改過了。
merge()
精彩的來了!
session.merge(detachedObj);
merge()主要用來對付一種討厭的情況或錯誤:org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [Book#10]
程式大概都是長這樣:
session = sf.openSession();
tx = session.beginTransaction();
List list = session.createCriteria(Book.class).list();
// 給你掛掉
session.update(aBook);
tx.commit();
aBook是detached obj,而list中也包括了與aBook相同id的另一個物件,所以在list之後,相同id的物件已經存在session cache中了,這時要把aBook再persistent到session裡就會掛掉,因為同一id的物件在同一session裡只能存有一份。
而一般在update detached obj之前會把同id的原物件取出來有幾種可能:做log或做欄位正確性檢查,因此在update之前執行get/list就無法避免了。
之前的作法就是用session.evict()將新取出來的物件踢出session,然後再update,但是這樣有個缺點,就是evict的動作得發生在business layer,造成business與dao的coupling。
session = sf.openSession();
tx = session.beginTransaction();
List list = session.createCriteria(Book.class).list();
// 麻煩的作法
session.evict(list);
// 沒事了
session.update(aBook);
tx.commit();
現在救星來了,改用merge()吧。
session = sf.openSession();
tx = session.beginTransaction();
List list = session.createCriteria(Book.class).list();
// 安全過關
aBook = session.merge(aBook);
tx.commit();
// 這個要特別注意
return aBook;
merge時,Hibernate會將detached obj的資料都copy到persistent obj裡,然後回傳persistent obj,所以detached obj還是detached。
所以回傳的那一筆跟原本在session cache裡那一筆是同一筆,而被merge的那一筆跟回傳的與原本在session cache裡那兩筆還是不同的。
被merge的 != 原本在session cache裡那一筆
被merge的 != 回傳的那一筆
原本在session cache裡那一筆 == 回傳的那一筆
例外狀況:
  • 當session cache裡沒有相同id的物件時,去db裡面找。
  • 當db裡找不到時,就新增一筆新的。
  • 當被merge的obj不是detached而是transient時,就新增一筆新的。

沒有留言:

張貼留言