對Java而言,要識別兩個物件是否為同一個物件有兩種方式,一種是根據物件是否擁有同樣的記憶體位置來決定,在Java語法中就是透過==
運算來比較,這是Java所定義的物件識別(Object identity),一種是根據equals()、hasCode()中的定義,這是Java所定義的物件相等(Object equality)。
先探討第一種Java的識別方式在Hibernate中該注意的地方,在Hibernate中,如果是在同一個session中根據相同查詢所得到的相同
資料,則它們會擁有相同的Java識別,舉個實際的例子來說明:
Session
session = sessions.openSession();
Object obj1 = session.load(User.class, new Integer(1)); Object obj2 = session.load(User.class, new Integer(1)); session.close(); System.out.println(obj1 == obj2); 上面這個程式片段將會顯示true的結果,表示obj1與obj2是參考至同一物件,但如果是以下的情況則會顯示false: Session
session1 = sessions.openSession();
Object obj1 = session1.load(User.class, new new Integer(1)); session1.close(); Session session2 = sessions.openSession(); Object obj2 = session2.load(User.class, new Integer(1)); session2.close(); System.out.println(obj1 == obj2); 原因可以參考 簡介快取(Session Level) 。 應用程式中基於效能的原因,不會在一個使用者的長時間操作會話階段,持續開始Session,將物件維持在 Persistence狀態,Hibernate並不保證不同時間所取得的資料物件,其是否參考至記憶體的同一位置,使用==來比較兩個物件的資料是否代 表資料庫中的同一筆資料是不可 行的。
再來討論物件相等的問題,在Java程式中要比較兩個物件是否相同,會透過equals()方法,而Object預設的
equals()本身是比較物件的記憶體參考,如果您要比較兩個物件的資料內容是否相同,您必須實作
equals()與hashCode(),最簡單的實作方式,是在equals()中,對物件的每個屬性逐一加以比較是否相同,稱之為by value equality。
討論一下Hibernate中資料識別問題,對資料庫而言,其識別一筆資料唯一性的方式是根據主鍵值,如果手上有兩份資料,它們擁有同樣的主鍵值,則它們在資料庫中代表同一個
欄位的資料。
由於主鍵值是資料庫中的資料唯一識別方式,因此Hibernate中的資料物件是否對應於一筆欄位資料,就是根據與主鍵值對應的物件識別屬 性(identifier property),Hibernate會維護物件的識別屬性,必要時,您可以將識別屬性的setter方法設定為private,以避免程式中遭到 修改,您可以藉由Session的getIdentifier()方法取得物件的識別屬性值。 如果要結合equals()、hashCode()來實作物件相等,一個根據資料庫的識別屬性的實作方式,是透過識別屬性的getter方法取得物件的識別屬性值並加以比較, 例如若id的型態是String,一個實作的例子如下: public
class User {
.... public boolean equals(Object o) { if(this == o) return true; if(id == null || !(o instanceof User)) return false; final User user == (User) o; return this.id.equals(user.getId()); } public int hashCode() { return id == null ? System.identityHashCode(this) : id.hashcode(); } } 這個例子取自於Hibernate in Action第123頁的範例,稱之為database identity equality,然而要注意的是,因為當一個物件被new出來而還沒有save()時,它並不會被賦予id值,如果您在物件儲存前,可能就有需求比較物件的相等性,就不適用這 個方法,例如物件若會被加入Set物件之中,該物件在被儲存至資料庫前與後,在Set中的判斷將有所不同,導致明明是同一個物件,卻使得程式出現不同的行為。 較好的方式是根據商務鍵值(Business key)來實作equals()與hashCode(),稱之為business key equality,在 Hibernate 官方參考手冊 中給了一個例子: public
class Cat {
... public boolean equals(Object other) { if (this == other) return true; if (!(other instanceof Cat)) return false; final Cat cat = (Cat) other; if (!getName().equals(cat.getName())) return false; if (!getBirthday().equals(cat.getBirthday())) return false; return true; } public int hashCode() { int result; result = getName().hashCode(); result = 29 * result + getBirthday().hashCode(); return result; } } 與by value equality的實作方式類似,但根據性的不同是不再比對所有的屬性,而是只比較商務鍵,商 務鍵是一個屬性或多個屬性的結合,對每個具有相同資料庫識別的物件來說,商務鍵的組合也是唯一的,商務鍵的挑選可以找那些從不為null、 immutable或很少改變且具有唯一性的屬性(例如對應欄位中UNIQUE的屬性),選用識別屬性作為商務屬性之一也是一種選擇。 願意的話,還可以使用org.apache.commons.lang.builder.EqualsBuilder與 org.apache.commons.lang.builder.HashCodeBuilder來協助定義equals()與hashCode(), 例如: package onlyfun.caterpillar; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; public class User { .... public boolean equals(Object obj) { if(obj == this) { return true; } if(!(obj instanceof User)) { return false; } User user = (User) obj; return new EqualsBuilder() .append(this.name, user.getName()) .append(this.phone, user.getPhone()) .isEquals(); } public int hashCode() { return new HashCodeBuilder() .append(this.name) .append(this.phone) .toHashCode(); } } |