天天看點

hibernate集合映射inverse和cascade詳解

1、到底在哪用cascade="..."?

cascade屬性并不是多對多關系一定要用的,有了它隻是讓我們在插入或删除對像時更友善一些,隻要在cascade的源頭上插入或是删除,所有cascade的關系就會被自己動的插入或是删除。便是為了能正确的cascade,unsaved-value是個很重要的屬性。Hibernate通過這個屬性來判斷一個對象應該save還是update,如果這個對象的id是unsaved-value的話,那說明這個對象不是persistence object要save(insert);如果id是非unsaved-value的話,那說明這個對象是persistence object(資料庫中已存在),隻要update就行了。saveOrUpdate方法用的也是這個機制。

2、到底在哪用inverse="ture"?

inverse屬性預設是false的,就是說關系的兩端都來維護關系。這個意思就是說,如有一個Student, Teacher和TeacherStudent表,Student和Teacher是多對多對多關系,這個關系由TeacherStudent這個表來表現。那麼什麼時候插入或删除TeacherStudent表中的記錄來維護關系呢?在用hibernate時,我們不會顯示的對TeacherStudent表做操作。對TeacherStudent的操作是hibernate幫我們做的。hibernate就是看hbm檔案中指定的是"誰"維護關系,那個在插入或删除"誰"時,就會處發對關系表的操作。前提是"誰"這個對象已經知道這個關系了,就是說關系另一頭的對象已經set或是add到"誰"這個對象裡來了。前面說過inverse預設是false,就是關系的兩端都維護關系,對其中任一個操作都會處發對表系表的操作。當在關系的一頭,如Student中的bag或set中用了inverse="true"時,那就代表關系是由另一關維護的(Teacher)。就是說當這插入Student時,不會操作TeacherStudent表,即使Student已經知道了關系。隻有當Teacher插入或删除時才會處發對關系表的操作。是以,當關系的兩頭都用inverse="true"是不對的,就會導緻任何操作都不處發對關系表的操作。當兩端都是inverse="false"或是default值是,在代碼對關系顯示的維護也是不對的,會導緻在關系表中插入兩次關系。

在一對多關系中inverse就更有意義了。在多對多中,在哪端inverse="true"效果差不多(在效率上)。但是在一對多中,如果要一方維護關系,就會使在插入或是删除"一"方時去update"多"方的每一個與這個"一"的對象有關系的對象。而如果讓"多"方面維護關系時就不會有update操作,因為關系就是在多方的對象中的,直指插入或是删除多方對象就行了。當然這時也要周遊"多"方的每一個對象顯示的操作修關系的變化展現到DB中。不管怎樣說,還是讓"多"方維護關系更直覺一些。

3、cascade和inverse有什麼差別?

可以這樣了解,cascade定義的是關系兩端對象到對象的級聯關系;而inverse定義的是關系和對象的級聯關系。

all : 所有情況下均進行關聯操作。

none:所有情況下均不進行關聯操作。這是預設值。

save-update:在執行save/update/saveOrUpdate時進行關聯操作。

delete:在執行delete時進行關聯操作。

all的意思是save-update + delete

all-delete-orphan 的意思是當對象圖中産生孤兒節點時,在資料庫中删除該節點

all比較好了解,舉個例子說一下all-delete-orphan:

Category與Item是一對多的關系,也就是說Category類中有個Set類型的變量items.

舉個例子,現items中存兩個Item, item1,item2,如果定義關系為all-delete-orphan

當items中删除掉一個item(比如用remove()方法删除item1),那麼被删除的Item類執行個體

将變成孤兒節點,當執行category.update(),或session.flush()時

hibernate同步緩存和資料庫,會把資料庫中item1對應的記錄删掉

//

///

4. hibernate如何根據pojo來更新資料庫

4.0  在commit/flush之前,hibernate不會對pojo對象作神秘的處理。

4.0.1 在select查詢出pojo時,hibernate根據“字段--屬性”的對應關系,用字段的值填充pojo的屬性;

然後根據“關系标記”生成sql語句從relationTable中查詢出滿足條件的relationPojo,并把這些relatinPojo

放到“關系屬性”中。這個過程是機械的。

4.0.2 在pojo對象被查出來後,到commit(或flush)之前,它将是一個普通的java對象,hibernate不會做額外的手腳。

比如,不會限制你設定一個屬性的值為null或其它任何值

在集合類Set的add(object)操作時, 不會改變object的值,不會檢查參數object是否是一個pojo對象

設定mainPojo的一個“橋屬性”的值,不會自動設定relationPojo的對應的“橋屬性”的值。

執行session.delete(pojo)時,pojo本身沒有變化,他的屬性值也沒有變化。

執行session.save(pojo)時,如果pojo的id不是hibernate或資料庫生成,則它的值沒有變化。

  如果pojo的id是hibernate或資料庫生成,則hibernate會把id給pojo設上去。

extend: 對lazy=true的set,hibernate在進行set的操作(調用java.util.Set中聲明的方法)時

會先inialize這個set,僅此而已。而inialize僅僅是從資料庫中撈出set的資料。

如果一個set已經被inialize了,那麼對它進行的操作就是java.util.Set接口中定義的語義。

另外,如果id由hibernate來生成,那麼在save(pojo)時,hibernate會改變該pojo,會設定它的id,這

可能改變該pojo的hashCode,詳細地讨論見帖《》

mapping檔案中标記的某些屬性及pojo對象的操作會對資料庫操作産生影響,這些影響都是在commit時才會起作用。

而在commit前pojo的狀态不受它們的影響。

不過,待commit之時,将由hibernate完全掌控,它好像知道pojo對象從建立到commit這中間的所有變化。

4.01. 關聯更新

"關系标記"對應的屬性是一個pojo或一個pojo的集合,修改“關系屬性”的值能會導緻更新mainTable表,也可能會更新relationTable表。

這種更新暫叫“關聯更新”。

4.1.inverse屬性的作用(假定沒有設定cascade屬性)

4.1.1 “隻有集合标記(set/map/list/array/bag)才有inverse屬性”。

————不妨以标記set為例,具體為“一個地區(Address表)的學校(School表)” -- address.schoolSet。

4.1.2 “set的inverse屬性決定是否把對set的改動反映到資料庫中去。

inverse=false————反映;inverse=true————不反映”

inverse屬性預設為false

對<one-to-many>和<many-to-many>子标記,這兩條都适用。

不管是對set做什麼操作,4.1.2都适用。

4.1.3當inverse=false時,hibernate如何将對set的改動反映到資料庫中:

對set的操作主要有:(1)新增元素 address.getSchoolSet().add(oneSchool);

(2)删除元素 address.getSchoolSet().remove(oneSchool);

(3)删除set  address.setSchoolSet(null);

(4)設新set  address.setSchoolSet( newSchoolSet);

(5)轉移set  otherSchoolSet = otherAddress.getSchoolSet();

  otherAddress.setSchoolSet(null);

  address.setSchoolSet(otherSchoolSet);

(6)改變set中元素的屬性的值  如果是改變key屬性,這會導緻異常

  如果改變的是普通的屬性,則hibernate認為set沒有變化(在後面可以看出緣由)。

  是以這種情形不予考慮。

改變set後,hibernate對資料庫的操作根據是<one-to-many>關系還是<many-to-many>關系而有不同。

對one-to-many,對school set的改動,會改變表SCHOOL中的資料:

  #SCHOOL_ID是school表的主鍵,SCHOOL_ADDRESS是school表中的位址欄位

  #表School的外鍵為SCHOOL_ADDRESS,它對應表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設定為null不知道有什麼實際意義?)

(33)delete 屬于某一address的所有school ————sqlDeleteString:

update SCHOOL set SCHOOL_ADDRESS=null where SCHOOL_ADDRESS=?

(44)update ————sqlUpdateRowString:"", no need

對many-to-many,對school set的改動,會改變關系表ADDRESS_SCHOOL中的資料:

#“地區————學校”的關系為多對多的關系有點牽強,隻是為了友善與上面的one-to-many作比較

#假設有一個關系表ADDRESS_SCHOOL,有兩個字段ADDRESS_ID, SCHOOL_ID,

#這兩個字段分别對應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=?

對set的操作(1),hibernate會執行(11)sqlInsertRowString

對set的操作(2),hibernate會執行(22)sqlDeleteRowString

對set的操作(3),hibernate會執行(33)sqlDeleteString

對set的操作(4),老的schoolSet因為沒有所屬的address,是以被全部delete掉,即先執行(33)sqlDeleteString

然後新增新的schoolSet,即再執行sqlInsertRowString

對set的操作(5),實際上就是将set從一個pojo轉移到另一pojo:

首先,執行sqlDeleteString,删除掉otherAddress所屬的school

然後,執行sqlDeleteString,删除掉address原先的school

最後,執行sqlInsertRowString,将otherSchoolSet新增給address

總結:(1)對one-to-many而言,改變set,會讓hibernate執行一系列的update語句, 不會delete/insert資料

(2)對many-to-many而言,改變set,隻修改關系表的資料,不會影響many-to-many的另一方。

(3)雖然one-to-many和many-to-many的資料庫操作不一樣,但目的都是一個:維護資料的一緻性。執行的sql都

隻涉及到“橋字段”,不會考慮或改變其他的字段,是以對set的操作(6)是沒有效果地。

extend:對list,可能還會維護index字段。

4.1.4 “inverse與cascade沒有什麼關系,互無牽扯。”

commit後,這兩個屬性發揮作用的時機不同,hibernate會根據對pojo對象的改動,及cascade屬性的設定,

生成一系列的Action,比如UpdateAction,DeleteAction,InsertAction等,每個Action都有execute方法以執行對應的sql語句。

待所有這些Action都生成好了後,hibernate再一起執行它們,在執行sql前,inverse屬性起作用,

當inverse=true時,不執行sql;當inverse=false時,執行sql。

4.1.5 inverse的預設值為false,是以inverse屬性預設會進行“關聯更新”。

4.1.6 建議:隻對set + many-to-many設定inverse=false,其他的标記不考慮inverse屬性。

   糟糕的是,不設定inverse屬性時,inverse預設為false。

4.2. 級聯(cascade)屬性的作用:

4.2.1 隻有“關系标記”才有cascade屬性:many-to-one,one-to-one ,any,

set(map, bag, idbag, list, array) + one-to-many(many-to-many)

4.2.2 級聯指的是當主要方執行操作時,關聯對象(被動方)是否同步執行同一操作。

pojo和它的關系屬性的關系就是“主要方 -- 被動方”的關系,如果關系屬性是一個set,那麼被動方就是set中的一個一個元素,。

比如:學校(School)有三個屬性:地區(Address),校長(TheMaster)和學生(Set, 元素為Student)

執行session.delete(school)時,級聯決定是否執行session.delete(Address),session.delete(theMaster),

是否對每個aStudent執行session.delete(aStudent)。

extend:這點和inverse屬性是有差別的。見4.3.

4.2.3 一個操作因級聯cascade可能觸發多個關聯操作。前一個操作叫“主要操作”,後一個操作叫“關聯操作”。

cascade屬性的可選值:

all : 所有情況下均進行關聯操作。

none:所有情況下均不進行關聯操作。這是預設值。

save-update:在執行save/update/saveOrUpdate時進行關聯操作。

delete:在執行delete時進行關聯操作。

具體執行什麼“關聯操作”是根據“主要操作”來的:

  “主要操作”         “關聯操作”

session.saveOrUpdate --> session.saveOrUpdate (執行saveOrUpdate實際上會執行save或者update)

session.save ----> session.saveOrUpdate

session.udpate --> session.saveOrUpdate

session.delete --> session.delete

4.2.4 主要操作和關聯操作的先後順序是“先儲存one,再儲存many;先删除many,再删除one;先update主要方,再update被動方”

對于one-to-one,當其屬性constrained="false"(預設值)時,它可看作one-to-many關系;

   當其屬性constrained="true"時,它可看作many-to-one關系;

對many-to-many,它可看作one-to-many。

比如:學校(School)有三個屬性:地區(Address),校長(TheMaster,其constrained="false")和學生(Set, 元素為Student)

當執行session.save(school)時,

實際的執行順序為:session.save(Address);

session.save(school);

session.save(theMaster);

for( 對每一個student ){

session.save(aStudent);

}

當執行session.delete(school)時,

實際的執行順序為:session.delete(theMaster);

for( 對每一個student ){

session.delete(aStudent);

}

session.delete(school);

session.delete(Address);

當執行session.update(school)時,

實際的執行順序為:session.update(school);

session.saveOrUpdate(Address);

session.saveOrUpdate(theMaster);

for( 對每一個student ){

session.saveOrUpdate(aStudent);

}

注意:update操作因級聯引發的關聯操作為saveOrUpdate操作,而不是update操作。

saveOrUpdate與update的差別是:前者根據操作對象是儲存了還是沒有儲存,而決定執行update還是save

extends: 實際中,删除學校不會删除地區,即地區的cascade一般設為false

另外,many-to-many關系很少設定cascade=true,而是設定inverse=false。這個反映了cascade和inverse的差別。見4.3

4.2.6 cascade的預設值為false,是以inverse屬性預設會進行“關聯更新”。

4.2.7 總結:級聯(cascade)就是操作一個對象時,對它的屬性(其cascade=true)也進行這個操作。

4.3 inverse和cascade的比較

這兩個屬性本身互不影響,但起的作用有些類似,都能引發對關系表的更新。

4.3.1 inverse隻對set+one-to-many(或many-to-many)有效,對many-to-one, one-to-one無效。

  cascade對關系标記都有效。

4.3.2 inverse對集合對象整體起作用,cascade對集合對象中的一個一個元素起作用,如果集合為空,那麼cascade不會引發關聯操作。

比如将集合對象置為null, school.setStudentSet(null)

inverse導緻hibernate執行:udpate STUDENT set SCHOOL_ID=null where SCHOOL_ID=?

cascade則不會執行對STUDENT表的關聯更新, 因為集合中沒有元素。

再比新增一個school, session.save(school)

inverse導緻hibernate執行:

for( 對(school的每一個student ){

udpate STUDENT set SCHOOL_ID=? where STUDENT_ID=? //将學生的school_id改為新的school的id

}

cascade導緻hibernate執行:

for( 對school的每一個student ){

session.save(aStudent); //對學生執行save操作

}

extends:如果改變集合中的部分元素(比如新增一個元素),

inverse: hibernate先判斷哪些元素改變了,對改變的元素執行相應的sql

cascade: 它總是對集合中的每個元素執行關聯操作。

(在關聯操作中,hibernate會判斷操作的對象是否改變)

4.3.2 兩個起作用的時機不同:

cascade:在對主要方操作時,級聯發生。

inverse: 在flush時(commit會自動執行flush),對session中的所有set,hibernate判斷每個set是否有變化,

對有變化的set執行相應的sql,執行之前,會有個判斷:if( inverse == true ) return;

可以看出cascade在先,inverse在後。

4.3.3 inverse 對set + one-to-many 和 set + many-to-many 起的作用不同。hibernate生成的sql不同。

  對one-to-many,hibernate對many方的資料庫表執行update語句。

  對many-to-many, hibernate對關系表執行insert/update/delte語句,注意不是對many方的資料庫表而是關系表。

  cascase 對set都是一緻的,不管one-to-many還是many-to-many。都簡單地把操作傳遞到set中的每個元素。是以它總是更新many

方的資料庫表。

4.3.4 建議:隻對set + many-to-many設定inverse=false,其他的标記不考慮inverse屬性,都設為inverse=true。

   對cascade,一般對many-to-one,many-to-many,constrained=true的one-to-one 不設定級聯删除。  

繼續閱讀