Hibernate 的 Proxy 陷阱
閑話少說。本文是閱讀下面這篇文章的總結(jié)。
http://blog.xebia.com/2008/03/08/advanced-hibernate-proxy-pitfalls/
首先,什么是Proxy?這個可以參考一下GoF (著名的軟件“四人幫”Gang of Four)提出的《設計模式》。簡而言之,就是一個子類,在父類的基礎上,實現(xiàn)一點附加功能,而將其他請求全都交給父類去實現(xiàn),因而從用戶角度看子類可以完成全部父類的功能以及子類自己附加的功能,所以可以代替父類使用。
Hibernate 使用 Proxy 實現(xiàn) Lazy Loading 功能。 比如 Person 類有個成員,也是 Person 類,叫做 boss。如果使用 Lazy Loading,在加載一個 Person 的時候,不會自動加載其 boss 的數(shù)據(jù),Hibernate 會自動給 Person 類生成一個 Proxy,將其 boss 的 ID 付給這個 Proxy。只有在訪問 boss 的具體數(shù)據(jù)的時候,這個 Proxy 才會自動調(diào)取數(shù)據(jù)。
一個有趣的現(xiàn)象。如果我們使用圖形化調(diào)試器(比如 Eclipse)。當我們點開調(diào)試器中的 boss 的實例查看其數(shù)據(jù)的時候,這個 Proxy 就會去調(diào)取數(shù)據(jù)。這可能會使得調(diào)試和實際執(zhí)行的行為不一致。
陷阱1:直接訪問成員變量
Proxy 會將函數(shù)訪問轉(zhuǎn)到其父類的同樣函數(shù)處理。但是 Proxy 自己是不會持有數(shù)據(jù)的,其自己的成員變量(比如 boss 的姓名)都是 null。 比如,一個 Person 的 equals 函數(shù)可能會這樣寫:
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj == this) {
return true;
}
if (!(obj instanceof Person)) {
return false;
}
return name.equals((Person)obj).name);
}
上面高亮的那句話看起來沒什么問題,也是常見寫法。可是,當被傳進來的 obj 可能是個 Proxy 的時候,直接訪問 obj 的 name 成員變量就不對了 - 因為這永遠是 null。因此,訪問另一個 obj 的成員變量時要用 getter / setter。
陷阱2:instanceOf
如果用 instanceOf 判斷一個對象是否是 Person 類,那么就沒有考慮到 Proxy 的情況。文章給出的建議是關(guān)掉 lazy loading,恐怕不是個很好的建議??梢钥紤]用 Class.isAssignableFrom 吧。
后記:
文章后面有些討論,就不再詳細討論或者翻譯了。有下面幾點可能有幫助(很有些人說了些 Hibernate 的風涼話呢):
“@Peter: Actually, you don’t need to write equals/hashcode for proxies for that reason: Hibernate will enforce that every entity is represented by a single instance in a session, also for proxies. It will lookup a proxy instance if needed in an internal map, that is bound to the session.”
“To know the class of the proxy object, you can use the method
Class getClassWithoutInitializingProxy(Object object)
form org.hibernate.proxy.HibernateProxyHelper
/**
* Get the class of an instance or the underlying class
* of a proxy (without initializing the proxy!). It is
* almost always better to use the entity name!
*/”
“Actually there are some architectural problems resulting from pitfall 2.
There are some cases where you don’t want to expose a (visible) setter which could initialize the object…
Read here:
http://www.bb242.de/2008/04/02/how-to-live-with-hibernate-proxies/
A pretty neat workaround some things is to work with get() instead of load().”
“Regarding Proxies in general: The Hibernate approach is the we-can-implement-it-on-a-weekend approach. Whereas JDO has featured a smarter approach (available in many JPA implementations), i.e. use byte code enhancement to implement the proxy functionality in the class itself, which solves most of the problems. (But you can’t design and implement that on a weekend.)”
“Bytecode-enhancing the actual class would be great but in runtime is hard to do because they need to be enhanced before they are ever used which is complex.
A simpler alternative is to create a proxy for the actual object itself and *not* have the “target” kludge. Simply use the fields in the proxy directly. This still does not properly solve the inheritance scheme but that is impossible with lazy loading anyway.
The simpler alternative has way less problems than the current implementation and *can* be built in an afternoon (my replacement for Hibernate called SPF has it, and it took that long
Sadly, Hibernate sucks bigtime and is not a good persistence framework at all; it is just the one that sucks the least…
”posted on 2010-01-11 12:15 bing 閱讀(3572) 評論(0) 編輯 收藏 所屬分類: Hibernate