企業(yè)級(jí)Java應(yīng)用程序常常把數(shù)據(jù)在Java對(duì)象和相關(guān)數(shù)據(jù)庫(kù)之間來(lái)回移動(dòng),從手工編寫SQL代碼到諸如Hibernate這樣成熟的對(duì)象關(guān)系映射(ORM)解決方案,有很多種方法可以實(shí)現(xiàn)這個(gè)過程。

 

        無(wú)論采用什么樣的技術(shù),一旦開始將Java對(duì)象持久存儲(chǔ)到數(shù)據(jù)庫(kù)中,身份將成為一個(gè)復(fù)雜且難以管理的課題??赡艹霈F(xiàn)的情況是:您實(shí)例化了兩個(gè)不同的對(duì)象,而它們卻代表數(shù)據(jù)庫(kù)中的同一行。為了解決這個(gè)問題,您可能采取的措施是在持久性對(duì)象中實(shí)現(xiàn)equals()和hashCode(),可是要恰當(dāng)?shù)貙?shí)現(xiàn)這兩個(gè)方法比乍看之下要有技巧一些。讓問題更糟糕的是,那些傳統(tǒng)的思路(包括Hibernate官方文檔所提倡的)對(duì)于新的項(xiàng)目并不一定能提出最實(shí)用的解決方案。

        對(duì)象身份在虛擬機(jī)(VM)中和在數(shù)據(jù)庫(kù)中的差異是問題滋生的溫床。在虛擬機(jī)中,您并不會(huì)得到對(duì)象的ID,您只是簡(jiǎn)單地持有對(duì)象的直接引用。而在幕后,虛擬機(jī)確實(shí)給每個(gè)對(duì)象指派了一個(gè)8字節(jié)大小的ID,這個(gè)ID才是對(duì)象的真實(shí)引用。當(dāng)您將對(duì)象持久存儲(chǔ)到數(shù)據(jù)庫(kù)中的時(shí)候,問題開始產(chǎn)生了。假定您創(chuàng)建了一個(gè)Person對(duì)象并將它存入數(shù)據(jù)庫(kù)(我們可以叫它person1)。而您的其他某段代碼從數(shù)據(jù)庫(kù)中讀取了這個(gè)Person對(duì)象的數(shù)據(jù),并將它實(shí)例化為另一個(gè)新的Person對(duì)象(我們可以叫它Person2)?,F(xiàn)在您的內(nèi)存中有了兩個(gè)映射到數(shù)據(jù)庫(kù)中同一行的對(duì)象。一個(gè)對(duì)象引用只能指向它們的其中一個(gè),可是我們需要一種方法來(lái)表示這兩個(gè)對(duì)象實(shí)際上表示著同一個(gè)實(shí)體。這就是(在虛擬機(jī)中)引入對(duì)象身份的原因。

       在Java語(yǔ)言中,對(duì)象身份是由每個(gè)對(duì)象都持有的equals()方法(以及相關(guān)的hashCode()方法)來(lái)定義的。無(wú)論兩個(gè)對(duì)象是否為同一個(gè)實(shí)例,equals()方法都應(yīng)該能夠判別出它們是否表示同一個(gè)實(shí)體。hashCode()方法和equals()方法有關(guān)聯(lián)是因?yàn)樗邢嗟鹊膶?duì)象都應(yīng)該返回相同的hashCode.默認(rèn)情況下,equals()方法僅僅比較對(duì)象引用。一個(gè)對(duì)象和它自身是相等的,而和其他任何實(shí)例都不相等。對(duì)于持久性對(duì)象來(lái)說,重寫這兩個(gè)方法,讓代表著數(shù)據(jù)庫(kù)中同一行的兩個(gè)對(duì)象被視為相等是很重要的。而這對(duì)于Java中Collection(Set、Map和List)的正確工作更是尤為重要。

為了闡明實(shí)現(xiàn)equal()和hashCode()的不同途徑,讓我們考慮一個(gè)準(zhǔn)備持久存儲(chǔ)到數(shù)據(jù)庫(kù)中的簡(jiǎn)單對(duì)象Person.

public class Person {private Long id;private Integer version;public Long getId() {return id;}public void setId(Long id) {this.id = id;}public Integer getVersion() {return version;}public void setVersion(Integer version) {this.version = version;}// person-specific properties and behavior}

       在這個(gè)例子中,我們遵循了同時(shí)持有id字段和version字段的最佳實(shí)踐。Id字段保存了在數(shù)據(jù)庫(kù)中作為主鍵使用的值,而version字段則是一個(gè)從0開始增長(zhǎng)的增量,隨著對(duì)象的每次更新而變化(這幫助我們避免并發(fā)更新的問題)。為了更清楚一些,讓我們看看允許Hibernate把這個(gè)對(duì)象持久存儲(chǔ)到數(shù)據(jù)庫(kù)的Hibernate映射文件:

PERSON_SEQ

        Hibernate映射文件指明了Person的id字段代表數(shù)據(jù)庫(kù)中的ID列(也就是說,它是PERSON表的主鍵)。包含在id標(biāo)簽中的unsaved-value="null"屬性告訴Hibernate使用id字段來(lái)判斷一個(gè)Person對(duì)象之前是否被保存過。ORM框架必須依靠這個(gè)來(lái)判斷保存一個(gè)對(duì)象的時(shí)候應(yīng)該使用SQL的INSERT子句還是UPDATE子句。在這個(gè)例子中,Hibernate假定一個(gè)新對(duì)象的id字段一開始為null值,當(dāng)它第一次被保存時(shí)id才被賦予一個(gè)值。generator標(biāo)簽告訴Hibernate當(dāng)對(duì)象第一次保存時(shí),應(yīng)該從哪里獲得指派的id.在這個(gè)例子中,Hibernate使用數(shù)據(jù)庫(kù)序列作為唯一ID的來(lái)源。最后,version標(biāo)簽告訴Hibernate使用Person對(duì)象的version字段進(jìn)行并發(fā)控制。Hibernate將會(huì)執(zhí)行樂觀鎖定方案,根據(jù)這個(gè)方案,Hibernate在保存對(duì)象之前會(huì)根據(jù)數(shù)據(jù)庫(kù)版本號(hào)檢查對(duì)象的版本號(hào)?!∥覀?