企業(yè)級(jí)Java應(yīng)用程序常常把數(shù)據(jù)在Java對象和相關(guān)數(shù)據(jù)庫之間來回移動(dòng),從手工編寫SQL代碼到諸如Hibernate這樣成熟的對象關(guān)系映射(ORM)解決方案,有很多種方法可以實(shí)現(xiàn)這個(gè)過程。
無論采用什么樣的技術(shù),一旦開始將Java對象持久存儲(chǔ)到數(shù)據(jù)庫中,身份將成為一個(gè)復(fù)雜且難以管理的課題??赡艹霈F(xiàn)的情況是:您實(shí)例化了兩個(gè)不同的對象,而它們卻代表數(shù)據(jù)庫中的同一行。為了解決這個(gè)問題,您可能采取的措施是在持久性對象中實(shí)現(xiàn)equals()和hashCode(),可是要恰當(dāng)?shù)貙?shí)現(xiàn)這兩個(gè)方法比乍看之下要有技巧一些。讓問題更糟糕的是,那些傳統(tǒng)的思路(包括Hibernate官方文檔所提倡的)對于新的項(xiàng)目并不一定能提出最實(shí)用的解決方案。
對象身份在虛擬機(jī)(VM)中和在數(shù)據(jù)庫中的差異是問題滋生的溫床。在虛擬機(jī)中,您并不會(huì)得到對象的ID,您只是簡單地持有對象的直接引用。而在幕后,虛擬機(jī)確實(shí)給每個(gè)對象指派了一個(gè)8字節(jié)大小的ID,這個(gè)ID才是對象的真實(shí)引用。當(dāng)您將對象持久存儲(chǔ)到數(shù)據(jù)庫中的時(shí)候,問題開始產(chǎn)生了。假定您創(chuàng)建了一個(gè)Person對象并將它存入數(shù)據(jù)庫(我們可以叫它person1)。而您的其他某段代碼從數(shù)據(jù)庫中讀取了這個(gè)Person對象的數(shù)據(jù),并將它實(shí)例化為另一個(gè)新的Person對象(我們可以叫它Person2)。現(xiàn)在您的內(nèi)存中有了兩個(gè)映射到數(shù)據(jù)庫中同一行的對象。一個(gè)對象引用只能指向它們的其中一個(gè),可是我們需要一種方法來表示這兩個(gè)對象實(shí)際上表示著同一個(gè)實(shí)體。這就是(在虛擬機(jī)中)引入對象身份的原因。
在Java語言中,對象身份是由每個(gè)對象都持有的equals()方法(以及相關(guān)的hashCode()方法)來定義的。無論兩個(gè)對象是否為同一個(gè)實(shí)例,equals()方法都應(yīng)該能夠判別出它們是否表示同一個(gè)實(shí)體。hashCode()方法和equals()方法有關(guān)聯(lián)是因?yàn)樗邢嗟鹊膶ο蠖紤?yīng)該返回相同的hashCode.默認(rèn)情況下,equals()方法僅僅比較對象引用。一個(gè)對象和它自身是相等的,而和其他任何實(shí)例都不相等。對于持久性對象來說,重寫這兩個(gè)方法,讓代表著數(shù)據(jù)庫中同一行的兩個(gè)對象被視為相等是很重要的。而這對于Java中Collection(Set、Map和List)的正確工作更是尤為重要。
為了闡明實(shí)現(xiàn)equal()和hashCode()的不同途徑,讓我們考慮一個(gè)準(zhǔn)備持久存儲(chǔ)到數(shù)據(jù)庫中的簡單對象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ù)庫中作為主鍵使用的值,而version字段則是一個(gè)從0開始增長的增量,隨著對象的每次更新而變化(這幫助我們避免并發(fā)更新的問題)。為了更清楚一些,讓我們看看允許Hibernate把這個(gè)對象持久存儲(chǔ)到數(shù)據(jù)庫的Hibernate映射文件:
Hibernate映射文件指明了Person的id字段代表數(shù)據(jù)庫中的ID列(也就是說,它是PERSON表的主鍵)。包含在id標(biāo)簽中的unsaved-value="null"屬性告訴Hibernate使用id字段來判斷一個(gè)Person對象之前是否被保存過。ORM框架必須依靠這個(gè)來判斷保存一個(gè)對象的時(shí)候應(yīng)該使用SQL的INSERT子句還是UPDATE子句。在這個(gè)例子中,Hibernate假定一個(gè)新對象的id字段一開始為null值,當(dāng)它第一次被保存時(shí)id才被賦予一個(gè)值。generator標(biāo)簽告訴Hibernate當(dāng)對象第一次保存時(shí),應(yīng)該從哪里獲得指派的id.在這個(gè)例子中,Hibernate使用數(shù)據(jù)庫序列作為唯一ID的來源。最后,version標(biāo)簽告訴Hibernate使用Person對象的version字段進(jìn)行并發(fā)控制。Hibernate將會(huì)執(zhí)行樂觀鎖定方案,根據(jù)這個(gè)方案,Hibernate在保存對象之前會(huì)根據(jù)數(shù)據(jù)庫版本號(hào)檢查對象的版本號(hào)?!∥覀?