hibernate集合映射cascade和inverse詳解
Posted on 2011-04-22 10:51 Astro.Qi 閱讀(237) 評(píng)論(0) 編輯 收藏 所屬分類: Hibernate1、到底在哪用cascade="..."?
cascade屬性并不是多對(duì)多關(guān)系一定要用的,有了它只是讓我們?cè)诓迦牖騽h除對(duì)像時(shí)更方便一些,只要在cascade的源頭上插入或是刪除,所有cascade的關(guān)系就會(huì)被自己動(dòng)的插入或是刪除。便是為了能正確的cascade,unsaved-value是個(gè)很重要的屬性。Hibernate通過這個(gè)屬性來判斷一個(gè)對(duì)象應(yīng)該save還是update,如果這個(gè)對(duì)象的id是unsaved-value的話,那說明這個(gè)對(duì)象不是persistence object要save(insert);如果id是非unsaved-value的話,那說明這個(gè)對(duì)象是persistence object(數(shù)據(jù)庫中已存在),只要update就行了。saveOrUpdate方法用的也是這個(gè)機(jī)制。
2、到底在哪用inverse="ture"?
inverse屬性默認(rèn)是false的,就是說關(guān)系的兩端都來維護(hù)關(guān)系。這個(gè)意思就是說,如有一個(gè)Student, Teacher和TeacherStudent表,Student和Teacher是多對(duì)多對(duì)多關(guān)系,這個(gè)關(guān)系由TeacherStudent這個(gè)表來表現(xiàn)。那么什么時(shí)候插入或刪除TeacherStudent表中的記錄來維護(hù)關(guān)系呢?在用hibernate時(shí),我們不會(huì)顯示的對(duì)TeacherStudent表做操作。對(duì)TeacherStudent的操作是hibernate幫我們做的。hibernate就是看hbm文件中指定的是"誰"維護(hù)關(guān)系,那個(gè)在插入或刪除"誰"時(shí),就會(huì)處發(fā)對(duì)關(guān)系表的操作。前提是"誰"這個(gè)對(duì)象已經(jīng)知道這個(gè)關(guān)系了,就是說關(guān)系另一頭的對(duì)象已經(jīng)set或是add到"誰"這個(gè)對(duì)象里來了。前面說過inverse默認(rèn)是false,就是關(guān)系的兩端都維護(hù)關(guān)系,對(duì)其中任一個(gè)操作都會(huì)處發(fā)對(duì)表系表的操作。當(dāng)在關(guān)系的一頭,如Student中的bag或set中用了inverse="true"時(shí),那就代表關(guān)系是由另一關(guān)維護(hù)的(Teacher)。就是說當(dāng)這插入Student時(shí),不會(huì)操作TeacherStudent表,即使Student已經(jīng)知道了關(guān)系。只有當(dāng)Teacher插入或刪除時(shí)才會(huì)處發(fā)對(duì)關(guān)系表的操作。所以,當(dāng)關(guān)系的兩頭都用inverse="true"是不對(duì)的,就會(huì)導(dǎo)致任何操作都不處發(fā)對(duì)關(guān)系表的操作。當(dāng)兩端都是inverse="false"或是default值是,在代碼對(duì)關(guān)系顯示的維護(hù)也是不對(duì)的,會(huì)導(dǎo)致在關(guān)系表中插入兩次關(guān)系。
在一對(duì)多關(guān)系中inverse就更有意義了。在多對(duì)多中,在哪端inverse="true"效果差不多(在效率上)。但是在一對(duì)多中,如果要一方維護(hù)關(guān)系,就會(huì)使在插入或是刪除"一"方時(shí)去update"多"方的每一個(gè)與這個(gè)"一"的對(duì)象有關(guān)系的對(duì)象。而如果讓"多"方面維護(hù)關(guān)系時(shí)就不會(huì)有update操作,因?yàn)殛P(guān)系就是在多方的對(duì)象中的,直指插入或是刪除多方對(duì)象就行了。當(dāng)然這時(shí)也要遍歷"多"方的每一個(gè)對(duì)象顯示的操作修關(guān)系的變化體現(xiàn)到DB中。不管怎樣說,還是讓"多"方維護(hù)關(guān)系更直觀一些。
3、cascade和inverse有什么區(qū)別?
可以這樣理解,cascade定義的是關(guān)系兩端對(duì)象到對(duì)象的級(jí)聯(lián)關(guān)系;而inverse定義的是關(guān)系和對(duì)象的級(jí)聯(lián)關(guān)系。
all : 所有情況下均進(jìn)行關(guān)聯(lián)操作。none:所有情況下均不進(jìn)行關(guān)聯(lián)操作。這是默認(rèn)值。
save-update:在執(zhí)行save/update/saveOrUpdate時(shí)進(jìn)行關(guān)聯(lián)操作。
delete:在執(zhí)行delete時(shí)進(jìn)行關(guān)聯(lián)操作。
all的意思是save-update + delete
all-delete-orphan 的意思是當(dāng)對(duì)象圖中產(chǎn)生孤兒節(jié)點(diǎn)時(shí),在數(shù)據(jù)庫中刪除該節(jié)點(diǎn)
all比較好理解,舉個(gè)例子說一下all-delete-orphan:
Category與Item是一對(duì)多的關(guān)系,也就是說Category類中有個(gè)Set類型的變量items.
舉個(gè)例子,現(xiàn)items中存兩個(gè)Item, item1,item2,如果定義關(guān)系為all-delete-orphan
當(dāng)items中刪除掉一個(gè)item(比如用remove()方法刪除item1),那么被刪除的Item類實(shí)例
將變成孤兒節(jié)點(diǎn),當(dāng)執(zhí)行category.update(),或session.flush()時(shí)
hibernate同步緩存和數(shù)據(jù)庫,會(huì)把數(shù)據(jù)庫中item1對(duì)應(yīng)的記錄刪掉
//////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////
4.0 在commit/flush之前,hibernate不會(huì)對(duì)pojo對(duì)象作神秘的處理。
4.0.1 在select查詢出pojo時(shí),hibernate根據(jù)“字段--屬性”的對(duì)應(yīng)關(guān)系,用字段的值填充pojo的屬性;
然后根據(jù)“關(guān)系標(biāo)記”生成sql語句從relationTable中查詢出滿足條件的relationPojo,并把這些relatinPojo
放到“關(guān)系屬性”中。這個(gè)過程是機(jī)械的。
4.0.2 在pojo對(duì)象被查出來后,到commit(或flush)之前,它將是一個(gè)普通的java對(duì)象,hibernate不會(huì)做額外的手腳。
比如,不會(huì)限制你設(shè)置一個(gè)屬性的值為null或其它任何值
在集合類Set的add(object)操作時(shí), 不會(huì)改變object的值,不會(huì)檢查參數(shù)object是否是一個(gè)pojo對(duì)象
設(shè)置mainPojo的一個(gè)“橋?qū)傩?#8221;的值,不會(huì)自動(dòng)設(shè)置relationPojo的對(duì)應(yīng)的“橋?qū)傩?#8221;的值。
執(zhí)行session.delete(pojo)時(shí),pojo本身沒有變化,他的屬性值也沒有變化。
執(zhí)行session.save(pojo)時(shí),如果pojo的id不是hibernate或數(shù)據(jù)庫生成,則它的值沒有變化。
如果pojo的id是hibernate或數(shù)據(jù)庫生成,則hibernate會(huì)把id給pojo設(shè)上去。
extend: 對(duì)lazy=true的set,hibernate在進(jìn)行set的操作(調(diào)用java.util.Set中聲明的方法)時(shí)
會(huì)先inialize這個(gè)set,僅此而已。而inialize僅僅是從數(shù)據(jù)庫中撈出set的數(shù)據(jù)。
如果一個(gè)set已經(jīng)被inialize了,那么對(duì)它進(jìn)行的操作就是java.util.Set接口中定義的語義。
另外,如果id由hibernate來生成,那么在save(pojo)時(shí),hibernate會(huì)改變?cè)損ojo,會(huì)設(shè)置它的id,這
可能改變?cè)損ojo的hashCode,詳細(xì)地討論見帖《》
mapping文件中標(biāo)記的某些屬性及pojo對(duì)象的操作會(huì)對(duì)數(shù)據(jù)庫操作產(chǎn)生影響,這些影響都是在commit時(shí)才會(huì)起作用。
而在commit前pojo的狀態(tài)不受它們的影響。
不過,待commit之時(shí),將由hibernate完全掌控,它好像知道pojo對(duì)象從創(chuàng)建到commit這中間的所有變化。
4.01. 關(guān)聯(lián)更新
"關(guān)系標(biāo)記"對(duì)應(yīng)的屬性是一個(gè)pojo或一個(gè)pojo的集合,修改“關(guān)系屬性”的值能會(huì)導(dǎo)致更新mainTable表,也可能會(huì)更新relationTable表。
這種更新暫叫“關(guān)聯(lián)更新”。
4.1.inverse屬性的作用(假定沒有設(shè)置cascade屬性)
4.1.1 “只有集合標(biāo)記(set/map/list/array/bag)才有inverse屬性”。
————不妨以標(biāo)記set為例,具體為“一個(gè)地區(qū)(Address表)的學(xué)校(School表)” -- address.schoolSet。
4.1.2 “set的inverse屬性決定是否把對(duì)set的改動(dòng)反映到數(shù)據(jù)庫中去。
inverse=false————反映;inverse=true————不反映”
inverse屬性默認(rèn)為false
對(duì)<one-to-many>和<many-to-many>子標(biāo)記,這兩條都適用。
不管是對(duì)set做什么操作,4.1.2都適用。
4.1.3當(dāng)inverse=false時(shí),hibernate如何將對(duì)set的改動(dòng)反映到數(shù)據(jù)庫中:
對(duì)set的操作主要有:(1)新增元素 address.getSchoolSet().add(oneSchool);
(2)刪除元素 address.getSchoolSet().remove(oneSchool);
(3)刪除set address.setSchoolSet(null);
(4)設(shè)新set address.setSchoolSet( newSchoolSet);
(5)轉(zhuǎn)移set otherSchoolSet = otherAddress.getSchoolSet();
otherAddress.setSchoolSet(null);
address.setSchoolSet(otherSchoolSet);
(6)改變set中元素的屬性的值 如果是改變key屬性,這會(huì)導(dǎo)致異常
如果改變的是普通的屬性,則hibernate認(rèn)為set沒有變化(在后面可以看出緣由)。
所以這種情形不予考慮。
改變set后,hibernate對(duì)數(shù)據(jù)庫的操作根據(jù)是<one-to-many>關(guān)系還是<many-to-many>關(guān)系而有不同。
對(duì)one-to-many,對(duì)school set的改動(dòng),會(huì)改變表SCHOOL中的數(shù)據(jù):
#SCHOOL_ID是school表的主鍵,SCHOOL_ADDRESS是school表中的地址欄位
#表School的外鍵為SCHOOL_ADDRESS,它對(duì)應(yīng)表Address的主鍵ADDRESS_ID
(11)insert oneSchool———— sqlInsertRowString:
update SCHOOL set SCHOOL_ADDRESS=? where SCHOOL_ID=?
(僅僅update foreign-key的值。)
(22)delete oneSchool———— sqlDeleteRowString:
update SCHOOL set SCHOOL_ADDRESS=null where SCHOOL_ID=?
(很奇怪,把foreign-key設(shè)置為null不知道有什么實(shí)際意義?)
(33)delete 屬于某一address的所有school ————sqlDeleteString:
update SCHOOL set SCHOOL_ADDRESS=null where SCHOOL_ADDRESS=?
(44)update ————sqlUpdateRowString:"", no need
對(duì)many-to-many,對(duì)school set的改動(dòng),會(huì)改變關(guān)系表ADDRESS_SCHOOL中的數(shù)據(jù):
#“地區(qū)————學(xué)校”的關(guān)系為多對(duì)多的關(guān)系有點(diǎn)牽強(qiáng),只是為了方便與上面的one-to-many作比較
#假設(shè)有一個(gè)關(guān)系表ADDRESS_SCHOOL,有兩個(gè)字段ADDRESS_ID, SCHOOL_ID,
#這兩個(gè)字段分別對(duì)應(yīng)ADDRESS和SCHOOL兩表的key
(11)insert的SQL語句為: insert into ADDRESS_SCHOOL(ADDRESS_ID, SCHOOL_ID)
values(?,?)
(22)delete的SQL語句為: delete from ADDRESS_SCHOOL
where ADDRESS_ID=? AND SCHOOL_ID=?
(33)delete all的SQL語句為: delete from ADDRESS_SCHOOL
where ADDRESS_ID=?
(44)update的sql語句為 ————sqlUpdateRowString:
update ADDRESS_SCHOOL set ADDRESS_ID=?
where ADDRESS_ID=? AND SCHOOL_ID=?
對(duì)set的操作(1),hibernate會(huì)執(zhí)行(11)sqlInsertRowString
對(duì)set的操作(2),hibernate會(huì)執(zhí)行(22)sqlDeleteRowString
對(duì)set的操作(3),hibernate會(huì)執(zhí)行(33)sqlDeleteString
對(duì)set的操作(4),老的schoolSet因?yàn)闆]有所屬的address,所以被全部delete掉,即先執(zhí)行(33)sqlDeleteString
然后新增新的schoolSet,即再執(zhí)行sqlInsertRowString
對(duì)set的操作(5),實(shí)際上就是將set從一個(gè)pojo轉(zhuǎn)移到另一pojo:
首先,執(zhí)行sqlDeleteString,刪除掉otherAddress所屬的school
然后,執(zhí)行sqlDeleteString,刪除掉address原先的school
最后,執(zhí)行sqlInsertRowString,將otherSchoolSet新增給address
總結(jié):(1)對(duì)one-to-many而言,改變set,會(huì)讓hibernate執(zhí)行一系列的update語句, 不會(huì)delete/insert數(shù)據(jù)
(2)對(duì)many-to-many而言,改變set,只修改關(guān)系表的數(shù)據(jù),不會(huì)影響many-to-many的另一方。
(3)雖然one-to-many和many-to-many的數(shù)據(jù)庫操作不一樣,但目的都是一個(gè):維護(hù)數(shù)據(jù)的一致性。執(zhí)行的sql都
只涉及到“橋字段”,不會(huì)考慮或改變其他的字段,所以對(duì)set的操作(6)是沒有效果地。
extend:對(duì)list,可能還會(huì)維護(hù)index字段。
4.1.4 “inverse與cascade沒有什么關(guān)系,互無牽扯。”
commit后,這兩個(gè)屬性發(fā)揮作用的時(shí)機(jī)不同,hibernate會(huì)根據(jù)對(duì)pojo對(duì)象的改動(dòng),及cascade屬性的設(shè)置,
生成一系列的Action,比如UpdateAction,DeleteAction,InsertAction等,每個(gè)Action都有execute方法以執(zhí)行對(duì)應(yīng)的sql語句。
待所有這些Action都生成好了后,hibernate再一起執(zhí)行它們,在執(zhí)行sql前,inverse屬性起作用,
當(dāng)inverse=true時(shí),不執(zhí)行sql;當(dāng)inverse=false時(shí),執(zhí)行sql。
4.1.5 inverse的默認(rèn)值為false,所以inverse屬性默認(rèn)會(huì)進(jìn)行“關(guān)聯(lián)更新”。
4.1.6 建議:只對(duì)set + many-to-many設(shè)置inverse=false,其他的標(biāo)記不考慮inverse屬性。
糟糕的是,不設(shè)置inverse屬性時(shí),inverse默認(rèn)為false。
4.2. 級(jí)聯(lián)(cascade)屬性的作用:
4.2.1 只有“關(guān)系標(biāo)記”才有cascade屬性:many-to-one,one-to-one ,any,
set(map, bag, idbag, list, array) + one-to-many(many-to-many)
4.2.2 級(jí)聯(lián)指的是當(dāng)主控方執(zhí)行操作時(shí),關(guān)聯(lián)對(duì)象(被動(dòng)方)是否同步執(zhí)行同一操作。
pojo和它的關(guān)系屬性的關(guān)系就是“主控方 -- 被動(dòng)方”的關(guān)系,如果關(guān)系屬性是一個(gè)set,那么被動(dòng)方就是set中的一個(gè)一個(gè)元素,。
比如:學(xué)校(School)有三個(gè)屬性:地區(qū)(Address),校長(TheMaster)和學(xué)生(Set, 元素為Student)
執(zhí)行session.delete(school)時(shí),級(jí)聯(lián)決定是否執(zhí)行session.delete(Address),session.delete(theMaster),
是否對(duì)每個(gè)aStudent執(zhí)行session.delete(aStudent)。
extend:這點(diǎn)和inverse屬性是有區(qū)別的。見4.3.
4.2.3 一個(gè)操作因級(jí)聯(lián)cascade可能觸發(fā)多個(gè)關(guān)聯(lián)操作。前一個(gè)操作叫“主控操作”,后一個(gè)操作叫“關(guān)聯(lián)操作”。
cascade屬性的可選值:
all : 所有情況下均進(jìn)行關(guān)聯(lián)操作。
none:所有情況下均不進(jìn)行關(guān)聯(lián)操作。這是默認(rèn)值。
save-update:在執(zhí)行save/update/saveOrUpdate時(shí)進(jìn)行關(guān)聯(lián)操作。
delete:在執(zhí)行delete時(shí)進(jìn)行關(guān)聯(lián)操作。
具體執(zhí)行什么“關(guān)聯(lián)操作”是根據(jù)“主控操作”來的:
“主控操作” “關(guān)聯(lián)操作”
session.saveOrUpdate --> session.saveOrUpdate (執(zhí)行saveOrUpdate實(shí)際上會(huì)執(zhí)行save或者update)
session.save ----> session.saveOrUpdate
session.udpate --> session.saveOrUpdate
session.delete --> session.delete
4.2.4 主控操作和關(guān)聯(lián)操作的先后順序是“先保存one,再保存many;先刪除many,再刪除one;先update主控方,再update被動(dòng)方”
對(duì)于one-to-one,當(dāng)其屬性constrained="false"(默認(rèn)值)時(shí),它可看作one-to-many關(guān)系;
當(dāng)其屬性constrained="true"時(shí),它可看作many-to-one關(guān)系;
對(duì)many-to-many,它可看作one-to-many。
比如:學(xué)校(School)有三個(gè)屬性:地區(qū)(Address),校長(TheMaster,其constrained="false")和學(xué)生(Set, 元素為Student)
當(dāng)執(zhí)行session.save(school)時(shí),
實(shí)際的執(zhí)行順序?yàn)椋簊ession.save(Address);
session.save(school);
session.save(theMaster);
for( 對(duì)每一個(gè)student ){
session.save(aStudent);
}
當(dāng)執(zhí)行session.delete(school)時(shí),
實(shí)際的執(zhí)行順序?yàn)椋簊ession.delete(theMaster);
for( 對(duì)每一個(gè)student ){
session.delete(aStudent);
}
session.delete(school);
session.delete(Address);
當(dāng)執(zhí)行session.update(school)時(shí),
實(shí)際的執(zhí)行順序?yàn)椋簊ession.update(school);
session.saveOrUpdate(Address);
session.saveOrUpdate(theMaster);
for( 對(duì)每一個(gè)student ){
session.saveOrUpdate(aStudent);
}
注意:update操作因級(jí)聯(lián)引發(fā)的關(guān)聯(lián)操作為saveOrUpdate操作,而不是update操作。
saveOrUpdate與update的區(qū)別是:前者根據(jù)操作對(duì)象是保存了還是沒有保存,而決定執(zhí)行update還是save
extends: 實(shí)際中,刪除學(xué)校不會(huì)刪除地區(qū),即地區(qū)的cascade一般設(shè)為false
另外,many-to-many關(guān)系很少設(shè)置cascade=true,而是設(shè)置inverse=false。這個(gè)反映了cascade和inverse的區(qū)別。見4.3
4.2.6 cascade的默認(rèn)值為false,所以inverse屬性默認(rèn)會(huì)進(jìn)行“關(guān)聯(lián)更新”。
4.2.7 總結(jié):級(jí)聯(lián)(cascade)就是操作一個(gè)對(duì)象時(shí),對(duì)它的屬性(其cascade=true)也進(jìn)行這個(gè)操作。
4.3 inverse和cascade的比較
這兩個(gè)屬性本身互不影響,但起的作用有些類似,都能引發(fā)對(duì)關(guān)系表的更新。
4.3.1 inverse只對(duì)set+one-to-many(或many-to-many)有效,對(duì)many-to-one, one-to-one無效。
cascade對(duì)關(guān)系標(biāo)記都有效。
4.3.2 inverse對(duì)集合對(duì)象整體起作用,cascade對(duì)集合對(duì)象中的一個(gè)一個(gè)元素起作用,如果集合為空,那么cascade不會(huì)引發(fā)關(guān)聯(lián)操作。
比如將集合對(duì)象置為null, school.setStudentSet(null)
inverse導(dǎo)致hibernate執(zhí)行:udpate STUDENT set SCHOOL_ID=null where SCHOOL_ID=?
cascade則不會(huì)執(zhí)行對(duì)STUDENT表的關(guān)聯(lián)更新, 因?yàn)榧现袥]有元素。
再比新增一個(gè)school, session.save(school)
inverse導(dǎo)致hibernate執(zhí)行:
for( 對(duì)(school的每一個(gè)student ){
udpate STUDENT set SCHOOL_ID=? where STUDENT_ID=? //將學(xué)生的school_id改為新的school的id
}
cascade導(dǎo)致hibernate執(zhí)行:
for( 對(duì)school的每一個(gè)student ){
session.save(aStudent); //對(duì)學(xué)生執(zhí)行save操作
}
extends:如果改變集合中的部分元素(比如新增一個(gè)元素),
inverse: hibernate先判斷哪些元素改變了,對(duì)改變的元素執(zhí)行相應(yīng)的sql
cascade: 它總是對(duì)集合中的每個(gè)元素執(zhí)行關(guān)聯(lián)操作。
(在關(guān)聯(lián)操作中,hibernate會(huì)判斷操作的對(duì)象是否改變)
4.3.2 兩個(gè)起作用的時(shí)機(jī)不同:
cascade:在對(duì)主控方操作時(shí),級(jí)聯(lián)發(fā)生。
inverse: 在flush時(shí)(commit會(huì)自動(dòng)執(zhí)行flush),對(duì)session中的所有set,hibernate判斷每個(gè)set是否有變化,
對(duì)有變化的set執(zhí)行相應(yīng)的sql,執(zhí)行之前,會(huì)有個(gè)判斷:if( inverse == true ) return;
可以看出cascade在先,inverse在后。
4.3.3 inverse 對(duì)set + one-to-many 和 set + many-to-many 起的作用不同。hibernate生成的sql不同。
對(duì)one-to-many,hibernate對(duì)many方的數(shù)據(jù)庫表執(zhí)行update語句。
對(duì)many-to-many, hibernate對(duì)關(guān)系表執(zhí)行insert/update/delte語句,注意不是對(duì)many方的數(shù)據(jù)庫表而是關(guān)系表。
cascase 對(duì)set都是一致的,不管one-to-many還是many-to-many。都簡單地把操作傳遞到set中的每個(gè)元素。所以它總是更新many
方的數(shù)據(jù)庫表。
4.3.4 建議:只對(duì)set + many-to-many設(shè)置inverse=false,其他的標(biāo)記不考慮inverse屬性,都設(shè)為inverse=true。
對(duì)cascade,一般對(duì)many-to-one,many-to-many,constrained=true的one-to-one 不設(shè)置級(jí)聯(lián)刪除。