ITEEDU

Hibernate Gossip: 数据识别(Data Identity)

讨论一下对象识别问题,对数据库而言,其识别一笔数据唯一性的方式是根据主键值,如果手上有两份数据,它们拥有同样的主键值,则它们在数据库中代表同一个 字段的数据。对Java而言,要识别两个对象是否为同一个对象有两种方式,一种是根据对象是否拥有同样的内存位置来决定,在Java语法中就是透过== 运算来比较,一种是根据equals()、hasCode()中的定义。

先探讨第一种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 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)

Hibernate并不保证不同时间所取得的数据对象,其是否参考至内存的同一位置,使用==来比较两个对象的数据是否代表数据库中的同一笔数据是不可 行的,事实上就算在Java程序中也不会这么比较两个对象是否相同,要比较两个对象是否相同,会透过equals()方法,而Object预设的 equals()本身即比较对象的内存参考,如果您要有必要比较透过查询后两个对象的数据是否相同(例如当对象被储存至Set时)您必须实作 equals()与hashCode()。

一个实作equals()与hashCode()的方法是根据数据库的identity,方法之一是透过getId()方法取得对象的id值并加以比较,例如若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页的范例,然而这是个不被鼓励的例子,因为当一个对象被new出来而还没有save()时,它并不会被赋予id值,这时候就不适用这个方法。

另一个比较被采用的方法是根据对象中真正包括的的属性值来作比较,在 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; 
    } 

}
这个例子不是简单的比较id属性,而是一个根据商务键值(Business
key)实作equals()与hasCode()的例子,当然留下的问题就是您如何在实作时利用相关的商务键值,这就要根据您实际的商务需求来决定了。     

愿意的话,还可以使用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();
    }
}