初步了解Hibernate與對(duì)象共事
Hibernate與對(duì)象是如何共事的呢?Hibernate是完整的對(duì)象/關(guān)系映射解決方案,它提供了對(duì)象狀態(tài)管理(state management)的功能,使開發(fā)者不再需要理會(huì)底層數(shù)據(jù)庫(kù)系統(tǒng)的細(xì)節(jié)。
也就是說,相對(duì)于常見的JDBC/SQL持久層方案中需要管理SQL語句,Hibernate采用了更自然的面向?qū)ο蟮囊暯莵沓志没疛ava應(yīng)用中的數(shù)據(jù)。
換句話說,使用Hibernate的開發(fā)者應(yīng)該總是關(guān)注對(duì)象的狀態(tài)(state),不必考慮SQL語句的執(zhí)行。這部分細(xì)節(jié)已經(jīng)由Hibernate掌管妥當(dāng),只有開發(fā)者在進(jìn)行系統(tǒng)性能調(diào)優(yōu)的時(shí)候才需要進(jìn)行了解。
1. Hibernate對(duì)象狀態(tài)(object states)
Hibernate定義并支持下列對(duì)象狀態(tài)(state):
瞬時(shí)(Transient) - 由new操作符創(chuàng)建,且尚未與Hibernate Session 關(guān)聯(lián)的對(duì)象被認(rèn)定為瞬時(shí)(Transient)的。瞬時(shí)(Transient)對(duì)象不會(huì)被持久化到數(shù)據(jù)庫(kù)中,也不會(huì)被賦予持久化標(biāo)識(shí)(identifier)。如果程序中沒有保持對(duì)瞬時(shí)(Transient)對(duì)象的引用,它會(huì)被垃圾回收器(garbage collector)銷毀。使用Hibernate Session可以將其變?yōu)槌志?Persistent)狀態(tài)。(Hibernate會(huì)自動(dòng)執(zhí)行必要的SQL語句)
持久(Persistent) - 持久(Persistent)的實(shí)例在數(shù)據(jù)庫(kù)中有對(duì)應(yīng)的記錄,并擁有一個(gè)持久化標(biāo)識(shí)(identifier)。 持久(Persistent)的實(shí)例可能是剛被保存的,或剛被加載的,無論哪一種,按定義對(duì)象都僅在相關(guān)聯(lián)的Session生命周期內(nèi)的保持這種狀態(tài)。
Hibernate會(huì)檢測(cè)到處于持久(Persistent)狀態(tài)的對(duì)象的任何改動(dòng),在當(dāng)前操作單元(unit of work)執(zhí)行完畢時(shí)將對(duì)象數(shù)據(jù)(state)與數(shù)據(jù)庫(kù)同步(synchronize)。開發(fā)者不需要手動(dòng)執(zhí)行UPDATE。將對(duì)象從持久(Persistent)狀態(tài)變成瞬時(shí)(Transient)狀態(tài)同樣也不需要手動(dòng)執(zhí)行DELETE語句。
脫管(Detached) 與持久(Persistent)對(duì)象關(guān)聯(lián)的Session被關(guān)閉后,對(duì)象就變?yōu)槊摴?Detached)的。對(duì)脫管(Detached)對(duì)象的引用依然有效,對(duì)象可繼續(xù)被修改。脫管(Detached)對(duì)象如果重新關(guān)聯(lián)到某個(gè)新的Session上, 會(huì)再次轉(zhuǎn)變?yōu)槌志?Persistent)的(Detached其間的改動(dòng)將被持久化到數(shù)據(jù)庫(kù))。
這個(gè)功能使得一種編程模型,即中間會(huì)給用戶思考時(shí)間(user think-time)的長(zhǎng)時(shí)間運(yùn)行的操作單元(unit of work)的編程模型成為可能。 我們稱之為應(yīng)用程序事務(wù),即從用戶觀點(diǎn)看是一個(gè)操作單元(unit of work)。
接下來我們來細(xì)致的討論下狀態(tài)(states)及狀態(tài)間的轉(zhuǎn)換(state transitions)(以及觸發(fā)狀態(tài)轉(zhuǎn)換的Hibernate方法)。
2. 使對(duì)象持久化
Hibernate認(rèn)為持久化類(persistent class)新實(shí)例化的對(duì)象是瞬時(shí)(Transient)的。我們可將瞬時(shí)(Transient)對(duì)象與session關(guān)聯(lián)而變?yōu)槌志?Persistent)的。
- DomesticCat fritz = new DomesticCat();
- fritz.setColor(Color.GINGER);
- fritz.setSex('M');
- fritz.setName("Fritz");
- Long generatedId = (Long) sess.save(fritz);
如果Cat的持久化標(biāo)識(shí)(identifier)是generated類型的,那么該標(biāo)識(shí)(identifier)會(huì)自動(dòng)在save()被調(diào)用時(shí)產(chǎn)生并分配給cat。如果Cat的持久化標(biāo)識(shí)(identifier)是assigned類型的,或是一個(gè)復(fù)合主鍵(composite key),那么該標(biāo)識(shí)(identifier)應(yīng)當(dāng)在調(diào)用save()之前手動(dòng)賦予給cat。 你也可以按照EJB3 early draft中定義的語義,使用persist()替代save()。
此外,你可以用一個(gè)重載版本的save()方法。
- DomesticCat pk = new DomesticCat();
- pk.setColor(Color.TABBY);
- pk.setSex('F');
- pk.setName("PK");
- pk.setKittens( new HashSet() );
- pk.addKitten(fritz);
- sess.save( pk, new Long(1234) );
如果你持久化的對(duì)象有關(guān)聯(lián)的對(duì)象(associated objects)(例如上例中的kittens集合)那么對(duì)這些對(duì)象(譯注:pk和kittens)進(jìn)行持久化的順序是任意的(也就是說可以先對(duì)kittens進(jìn)行持久化也可以先對(duì)pk進(jìn)行持久化),除非你在外鍵列上有NOT NULL約束。 Hibernate不會(huì)違反外鍵約束,但是如果你用錯(cuò)誤的順序持久化對(duì)象(譯注:在pk持久之前持久kitten),那么可能會(huì)違反NOT NULL約束。
通常你不會(huì)為這些細(xì)節(jié)煩心,因?yàn)槟愫芸赡軙?huì)使用Hibernate的傳播性持久化(transitive persistence)功能自動(dòng)保存相關(guān)聯(lián)那些對(duì)象。 這樣連違反NOT NULL約束情況都不會(huì)出現(xiàn)了Hibernate會(huì)管好所有的事情。傳播性持久化(transitive persistence)將在本章稍后討論。
3. 裝載對(duì)象
如果你知道某個(gè)實(shí)例的持久化標(biāo)識(shí)(identifier),你就可以使用Session的load()方法 來獲取它。 load()的另一個(gè)參數(shù)是指定類的.class對(duì)象。 本方法會(huì)創(chuàng)建指定類的持久化實(shí)例,并從數(shù)據(jù)庫(kù)加載其數(shù)據(jù)(state)。
- Cat fritz = (Cat) sess.load(Cat.class, generatedId);
- // you need to wrap primitive identifiers
- long pkId = 1234;
- DomesticCat pk = (DomesticCat) sess.load( Cat.class, new Long(pkId) );
此外, 你可以把數(shù)據(jù)(state)加載到指定的對(duì)象實(shí)例上(覆蓋掉該實(shí)例原來的數(shù)據(jù))。
- Cat cat = new DomesticCat();
- // load pk's state into cat
- sess.load( cat, new Long(pkId) );
- Set kittens = cat.getKittens();
請(qǐng)注意如果沒有匹配的數(shù)據(jù)庫(kù)記錄,load()方法可能拋出無法恢復(fù)的異常(unrecoverable exception)。 如果類的映射使用了代理(proxy),load()方法會(huì)返回一個(gè)未初始化的代理,直到你調(diào)用該代理的某方法時(shí)才會(huì)去訪問數(shù)據(jù)庫(kù)。 若你希望在某對(duì)象中創(chuàng)建一個(gè)指向另一個(gè)對(duì)象的關(guān)聯(lián),又不想在從數(shù)據(jù)庫(kù)中裝載該對(duì)象時(shí)同時(shí)裝載相關(guān)聯(lián)的那個(gè)對(duì)象,那么這種操作方式就用得上的了。 如果為相應(yīng)類映射關(guān)系設(shè)置了batch-size, 那么使用這種操作方式允許多個(gè)對(duì)象被一批裝載(因?yàn)榉祷氐氖谴恚瑹o需從數(shù)據(jù)庫(kù)中抓取所有對(duì)象的數(shù)據(jù))。
如果你不確定是否有匹配的行存在,應(yīng)該使用get()方法,它會(huì)立刻訪問數(shù)據(jù)庫(kù),如果沒有對(duì)應(yīng)的行,會(huì)返回null。
- Cat cat = (Cat) sess.get(Cat.class, id);
- if (cat==null) {
- cat = new Cat();
- sess.save(cat, id);
- }
- return cat;
你甚至可以選用某個(gè)LockMode,用SQL的SELECT ... FOR UPDATE裝載對(duì)象。 請(qǐng)查閱API文檔以獲取更多信息。
- Cat cat = (Cat) sess.get(Cat.class, id, LockMode.UPGRADE);
注意,任何關(guān)聯(lián)的對(duì)象或者包含的集合都不會(huì)被以FOR UPDATE方式返回, 除非你指定了lock或者all作為關(guān)聯(lián)(association)的級(jí)聯(lián)風(fēng)格(cascade style)。
任何時(shí)候都可以使用refresh()方法強(qiáng)迫裝載對(duì)象和它的集合。如果你使用數(shù)據(jù)庫(kù)觸發(fā)器功能來處理對(duì)象的某些屬性,這個(gè)方法就很有用了。
- sess.save(cat);
- sess.flush(); //force the SQL INSERT
- sess.refresh(cat); //re-read the state (after the trigger executes)
此處通常會(huì)出現(xiàn)一個(gè)重要問題: Hibernate會(huì)從數(shù)據(jù)庫(kù)中裝載多少東西?會(huì)執(zhí)行多少條相應(yīng)的SQLSELECT語句? 這取決于抓取策略(fetching strategy),這里我們不再做以解釋。
【編輯推薦】