1. 程式人生 > >Hibernate 5.3 (四)

Hibernate 5.3 (四)

Hibernate 關聯對映

客觀世界中的物件很少有孤立存在的,這種例項物件之間的互相訪問就是關聯關係。

單向關係:只需單向訪問關聯端。例如,只能通過老師訪問學生,或者只能通過學生訪問老師。 雙向關係:關聯的兩端可以互相訪問。例如,老師和學生之間可以互相訪問。

關聯對映的類別

單向關聯:

  • 單向1- 1
  • 單向1-N
  • 單向N-1
  • 單向N-N

雙向關聯:

  • 雙向1-1
  • 雙向1-N
  • 雙向N-N

注意:雙向關係裡沒有N- 1,因為雙向關係1 -N和N- 1是完全相同的

單向 N-1 關聯

訪問順序就是應該是從N方去訪問1方。

N-1的關係在配置的時候,使用many-to-one元素,必須擁有name屬性。

可選屬性如下:

  • column:該屬性指定進行關聯的外來鍵列的列名, 該列名預設與該屬性同名。
  • class:該屬性指定關聯實體(持久化類)的全限定類名,預設是通過反射得到該屬性所屬類的類名。
  • cascade:該屬性指定哪些持久化操作會從主表記錄級聯到子表記錄。
  • fetch:該屬性指定Hibernate的抓取策略,該屬性值只能是join (使用外連線抓取)和select(使用選擇抓取)兩個值的其中之一。

比如現在有一個學生和班級的關聯關係,你要通過某個學生查詢班級的關係,下圖是對應兩個抓取策略的:

在這裡插入圖片描述

  • update, insert: 指定對應的欄位是否包含在用於Hibernate生成的update或insert語句中,兩個屬性的預設值都是true。 如果將二者指定為false,則表明這是一個純粹的外源關聯,它的值是通過對映同一個(或多個)欄位的其他屬性得到的,或由觸發器或其他程式來負責生成。(我們正常理解,就是如果設定update、insert 這個屬性是不可以插入和更新)
  • property-ref:指定關聯類的一個屬性,這個屬性將會和本類的外來鍵相對應(當外來鍵參照唯一鍵時需指定該屬性)。如果沒有指定,直接使用對方關聯類的主鍵。
  • unique:指定Hibernate通過DDL為外來鍵列新增唯一約束。 此外,也可以用做property-ref的目標屬性。 這使關聯同時具有一對一的效果。
  • not-null: 指定使用DDL為外來鍵欄位新增非空約束。
  • optimistic-lock:該屬性指定其更新時是否需要獲得樂觀鎖定,也就是其值決定引用關聯實體的屬性發生改變時版本值是否增長。
  • lazy:指定引用關聯實體的延遲載入特性,該屬性只能接受false、proxy、 no-proxy 三個值。該屬性預設值是proxy。實際上,Hibernate 預設會啟動單例項關聯(即多對一、一對一)的代理,指定 no-proxy 即要求該屬性應該在例項變數第一次被訪問時採用延遲抓取,lazy="false"指定此關聯實體總是被預先抓取。
  • not-found: 該屬性指定當外來鍵參照的主表記錄不存在時如何處理。該屬性只接受ignore 和exception兩個值,預設是exception,即不存在時丟擲異常;如果程式希望Hibernate忽略這個異常,則可指定該屬性值為ignore。
  • formula: 指定一一個SQL表示式,該外來鍵值將根據該SQL表示式來計算。
使用例子(基於外來鍵)

關聯關係:多個人對應一個地址。

public class Person {
	private Integer id;
	private Integer age;
	private Address address;// 這裡的屬性不是一個元件屬性,而是一個持久化類,這個是和之前不同
      //省略set、get方法,需自己補充

}

	 private Integer addressid;
         private String addressdetail;
         //省略set、get方法、自行補充
	
<class name="Person" table="PERSON" dynamic-insert="true" dynamic-update="false">
    <id name="id" type="integer" >
    <generator class="identity"></generator>
    </id>
    <property name="age" type="integer"></property>
    <!-- 這裡的N-1 是持久類之間的對映關係 ,N對應的是從表,1 對應的是主表-->
    <!-- column 該屬性的列名-->
      <!--cascade 這裡的級聯,對應到資料庫中是級聯更新和級聯刪除,級聯更新是在更新的時候,優先更新主表,在更新從表,級聯刪除是在刪除的時候,先刪除從表,在刪除主表 -->
    <many-to-one name="address" cascade="all" class="Address" column="address_id"></many-to-one>
    <!-- 在這裡address 對應的屬性應該對映成外來鍵列,這裡實際還要指定該外來鍵列和主表哪一列關聯property_ref指定,預設是和主表的主鍵關聯 -->
</class>
<class name="Address" table="ADDRESS">
    <id name="addressid" column="address_id">
    <generator class="identity" ></generator>
    </id>
    <property name="addressdetail" />
</class>
 		   Person p = new Person();
		   p.setAge(20);
		   com.example.test.bean.Address a = new com.example.test.bean.Address();
		   a.setAddressdetail("大中路");
		   p.setAddress(a);
		   ss.save(p);//在前面的配置檔案中,我已經說過N對應的是從表,1對應的是主表,我們這裡認為
		   //一個1個地址,對應多個人,person是從表,你在插入從表的時候需要依照主表的相關聯的鍵插入,可以
		   //在這看address 主表,還沒有持久化,只是一個瞬態,所以丟擲異常TransientObjectException。解決的辦法就是在從表的對映檔案
		   //中新增cascade="all",這時會先執行插入主表,然後插入從表,就不會有問題的。

注意:從表的插入更新都是相對於主表的,參看不了主表,你搞個捶捶。(刪除的時候,就要反著來,先刪除從表,咋在刪主表)

上面這個例子在處理單向N-1的關係的時候,在N方加入一個外來鍵,即可實現關聯,但是有時也會在資料庫新建一個表來維護這種關係,看下面的例子。

使用例子(基於關係維護表)

關聯關係:多個人對應一個地址。

修改Person.hbm.xml

<join table="person_address"><!-- table 用於指定關聯表 -->
     <key column="id" ></key><!-- 對映表參考主表的主鍵作為外來鍵列,也是該表的主鍵列 -->
     <many-to-one name="address" cascade="all" class="Address" fetch="join"   column="address_id"></many-to-one>
 </join>

這時生成的關聯表,兩個欄位都是以外來鍵列存在的。

使用<join…>元素對映連線表時還需要外來鍵關聯, 應在配置檔案中增加<ke…/>子元素來對映外來鍵,併為join元素增加<many-to-on…/>子元素,用於對映N- 1的關聯實體。該<many-to-on…/> 子元素的用法與不使用連線表的<many-to-on…/>子元素的用法完全相同。

單向的1-1

訪問的順序:你在某個持久類A中定義另外一個持久化類B的實體,順序就是A-B,A是從表,B是主表。

單向的1-1 關係和單向N-1的對映配置基本類似,只需要在標籤中 增加元素unique ,控制N端唯一,不就是1-1關係了。

使用例子(基於外來鍵)

關聯關係:一個人對應一個對應一個地址

持久類從上,不做任何修改。

修改Person.hbm.xml如下:

    <many-to-one name="address" cascade="all" class="Address" fetch="join"  **unique="true"** column="address_id"></many-to-one>

新增unique 之後,表中會生成一個唯一索引來表示。

在從表的Person 會生成主表的外來鍵列address_id,預設指向主表的主鍵(如果不指定property-ref)。

使用例子(基於關聯表)

關聯關係:一個人對應一個地址。

持久化類不動,修改Person.hbm.xml:

 <join table="person_address">
    <key column="id" ></key><!-- 對映表參考主鍵表的主鍵作為外來鍵列 ,也是該表的主鍵列-->
     <many-to-one name="address" cascade="all" class="Address" fetch="join"  unique="true"  column="address_id"></many-to-one>
  </join>

基於主鍵單向1-1

1-1的關聯可以基於主鍵關聯,基於主鍵關聯的持久化類不能擁有自己的主鍵生成器策略,它的主鍵由關聯實體來負責生成

採用基於主鍵的1一1關聯時,應使用 元素來對映關聯實體,配置元素時需指定一個name屬性,其值為關聯實體屬性的屬性名。除此之外,該元素還可接受如下幾個可選屬性:

  • class:該屬性指定關聯實體的全限定類名,預設是通過反射得到該屬性所屬類的類名。
  • cascade:該屬性指定哪些操作會從主表記錄級聯到子表記錄。
  • constrained:表明該類對應的表,和被關聯的物件所對應的資料庫表之間,通過一個外來鍵引用對主鍵進行約束。這個選項影響save()和 delete()在級聯執行時的先後順序,以及決定該關聯能否被委託(也在 schema export tool中被使用)。
  • fetch:該屬性設定抓取策略,該屬性接受join(外連線抓取)和 select(選擇抓取)兩個值的其中之一。
  • property-ref:指定關聯類的一個屬性,這個屬性將會和本類的主鍵相對應。如果沒有指定,預設使用對方關聯類的主鍵。(該值是指定的持關聯久化類的某一個屬性,該屬性主要用在select 的join 查詢的時候,比較多,以為兩表的聯合查詢,需要關聯某一個欄位)
  • access:指定 Hibernate訪問該關聯屬性的訪問策略,預設是 property。
  • lazy:指定引用關聯實體的延遲載入特性,該屬性只能接受 false、 proxy、no- proxy三個值。該屬性預設值是 proxy。實際上, Hibernate預設會啟動單例項關聯(即多對一、一對一)的代理,指定no- proxy即要求該屬性應該在例項變數第一次被訪問時採用延遲抓取,lazy=" false"指定此關聯實體總是被預先抓取。
使用例子
     <id name="id" type="integer" >
    <generator class="foreign"><!--主鍵的生成原則是參考主表的主鍵去生成  -->
    <param name="property">address</param><!-- 這裡的值要和 <one-to-one>中name一致 -->
    </generator>
    </id>
    <one-to-one name="address" class="Address" cascade="all"></one-to-one>

這樣的話,person表主鍵值會參看address表的主鍵值。這樣也是一種一對一的實現。

單向1-N關聯

單向1-N關聯的持久化類發生了改變,持久化類裡需要使用集合屬性。因為1的一端需要訪問N的一端,而N的一端將以集合(Set)形式表現。從這個意義上來看,1-N(實際上還包括N-N)和前面的集合對映非常相似,只是將前面用於對映集合元素的 元素改為(裡面是對映持久化類)使用元素。

使用元素可以指定如下兩個可選屬性:

  • class屬性:該屬性指定了關聯實體的型別,預設通過反射來獲取關聯實體的型別,如果集合屬性沒有使用泛型,則必須指定該屬性。
  • not-found:該屬性值只能是 exception或 Ignore,指定當從表記錄參照的主表記錄不存在時,Hibernate如何處理這種情況。該屬性值預設是 exception,即丟擲一個異常;如果程式希望Hibernate忽略這個異常,可以指定 not-found=" Ignore
使用例子(基於外來鍵)

關聯關係:一個人有多個地址。

修改Person 持久化類,新增如下屬性:

private Set<Address> address = new HashSet<Address>();
//自行提供set、get方法
 <set name="address" cascade="all">
      <key column="id"></key><!--外來鍵 持久化類和集合屬性的關聯 這個外來鍵是在從表Address 這個表中  -->
      <one-to-many class="Address" not-found="exception"></one-to-many> 
 </set> 
使用例子(基於連線表)

對於有連線表的1 -N關聯對映,對映檔案不再使用元素對映關聯實體,而是使用元素,但為了保證當前實體是1的一端,因此要為元素指定unique=“true”。

使用<many-to-many…/>元素時還可指定如下幾個可選屬性:

  • class:指定關聯實體的類名,預設由Hibernate通過反射來獲取該類名。
  • not-found:該屬性指定當外來鍵參照的主表記錄不存在時如何處理。該屬性只接受ignore 和exception兩個值,預設是exception, 即不存在時丟擲異常;如果程式希望Hibernate忽略這個異常,則可指定該屬性值為ignore。
  • formula:指定-一個SQL表示式, 該外來鍵值將根據該SQL表示式來計算。
  • outer-join:指定Hibernate是否要啟動外連線來抓取關聯實體,該屬性只能是true、false 和auto三個值之一,其中true表示啟用,false 表示不啟用,auto 表示由程式來決定。
  • fetch:該屬性指定Hibernate的抓取策略,該屬性值只能是join (使用外連線抓取)和select(使用選擇抓取)兩個值的其中之一。
  • lazy:指定Hibernate是否需要啟動延遲載入來載入關聯實體。unique:指定本持久化實體是否增加唯一一約束, 預設是false。
  • where: 該屬性指定一個SQL表示式, 指定當查詢、獲取關聯實體時的過濾條件,只有滿足該where條件的關聯實體才會被載入。
  • order-by:該屬性用於設定資料庫對集合元素排序,該屬性僅對1.4或更高版本的JDK有效。該屬性的值為指定表的指定欄位(一個或幾個)加上asc或者desc關鍵字,這種排序是資料庫執行SQL查詢時進行的排序,而不是直接在記憶體中排序。
  • property-ref:指定關聯類的一一個屬性, 這個屬性將會和本類的外來鍵相對應(當外來鍵參照唯一鍵時需指定該屬性)。如果沒有指定,直接使用對方關聯的主鍵。
 <set name="address" table="peson_address" cascade="all"><!-- 這裡1-N 就是不是使用<join>標籤 -->
 <!-- cascade 設定級聯更新、級聯刪除,否則會出現異常,前面講過的 -->
    <key column="id"></key><!--外來鍵 關聯person 的主鍵列,並且作為該表的主鍵列  -->
    <many-to-many  class="Address" fetch="join" unique="true " column="addres_id"></many-to-many>
    <!--預設的property-ref是指向關聯表的主鍵 -->
 </set>

單向的N-N關聯

N-N關聯必須使用連線表(為啥要用連線表,不可以用外來鍵呢,你這麼想1-N 可以在N方新增1方的主鍵作為外來鍵,而這時1變成N,外來鍵只能有一個值,當時不可以,必須用關聯表了),N-N關聯與有連線表的1-N關聯非常相似,只要去掉<many-to-many…/>元素的unique= ="true"屬性即可。

使用例子(基於關聯表)

一個人有多個地址,一個地址屬於多個人。這種帶有屬於關係就是N-N關聯。

持久化類和1-N的一樣,修改Person.hbm.xml:

 <set name="address" table="peson_address" cascade="all"><!-- 這裡1-N 就是不是使用<join>標籤 -->
 <!-- cascade 設定級聯更新、級聯刪除,否則會出現異常,前面講過的 -->
    <key column="id"></key><!--外來鍵 關聯person 的主鍵列,並且作為該表的主鍵列  -->
    <many-to-many  class="Address" fetch="join"  column="addres_id"></many-to-many>
    <!--預設的property-ref是指向關聯表的主鍵 -->
 </set>
		   Person p = new Person();
		   p.setAge(20);
		   Set<com.example.test.bean.Address> set = new java.util.HashSet<Address>();
		   com.example.test.bean.Address a = new com.example.test.bean.Address();
		   a.setAddressdetail("大中路");
		   com.example.test.bean.Address a1 = new com.example.test.bean.Address();
		   a1.setAddressdetail("大中路1");
		   set.add(a);
		   set.add(a1);
		   p.setAddress(set);
		   Person p1 = new Person();
		   p1.setAge(12);
		   p1.setAddress(set);
		   ss.save(p1);
		   ss.save(p);

生成的關聯表中兩個外來鍵,都是參考於兩張主表的主鍵。

雙向的1-N

雙向的1-N關聯與N- 1關聯是完全相同的兩種情形,兩端都需要增加對關聯屬性的訪問,N的端增加引用到關聯實體的屬性,1的一端增加集合屬性,集合元素為關聯實體。

使用例子(基於外來鍵)

Person 持久化類新增:

	private Set<Address> address = new HashSet<Address>();
	//自行新增set、get方法

Address 持久化類新增:

         private Person p;
         //自行補充set、get方法

修改Person.hbm.xml

<set name="address"  inverse="true">
 <key column="id"></key><!-- 外來鍵 持久化類和集合關聯  在集合這張表address中以person表的主鍵 作為外來鍵 -->
 <one-to-many class="Address"  ></one-to-many>
 </set>

 <set name="address"  inverse="true">
 <!-- 這裡我們沒有級聯操作 ,對於雙向的1 - N關聯對映,通常不應該允許1的一
 端控制關聯關係,而應該由N的一 端來控制關聯關係,此時我們可以在<set>元素中指定inverse= ="true",
  用於指定1的一端不控制關聯關係-->
 <key column="id"></key><!-- 外來鍵 持久化類和集合關聯  在集合這張表address中以person表的主鍵 作為外來鍵 -->
 <one-to-many class="Address"  ></one-to-many>
 </set>

修改Address.hbm.xml:

 <many-to-one name="p" class="Person" column="id" ></many-to-one><!-- property-ref 指向關聯表person 的主鍵的值 -->
    <!--  底層資料庫為了記錄這種1 -N關聯關係,實際上只需要在N的一端的資料表裡增加一個外來鍵列即可。
    這裡就存在一個問題:雙向對映時<many-to-one.../>元素將對映到外來鍵列,而<set..>元素 裡的<key/> 
    子元素也用於對映外來鍵列,其他的都對映同一列,因此對映檔案應為這兩個元素指定column屬性,並讓兩個column屬性值相同。
     一致  -->
                  Person p = new Person();
		   p.setAge(20);
		   ss.save(p);
		   com.example.test.bean.Address a = new com.example.test.bean.Address();
		   a.setAddressdetail("大中路");
		   ss.save(a);
		   com.example.test.bean.Address a1 = new com.example.test.bean.Address();
		   a1.setAddressdetail("大中路1");
		   ss.save(a1);
		   a1.setP(p);//由address 去設定關聯關係
		   a.setP(p);

這裡在操作的時候,就需要注意,因為沒有使用級聯操作,所以有些不同,同時需要注意的是:

  1. 首先還是要持久化Person,沒有主表,那你持久化從表,怎麼參考主表的主鍵列。
  2. 先設定Person和Address的關聯關係,再儲存持久化Address物件。如果順序反過來,程式持久化Address物件時,該Address物件還沒有關聯實體,所以Hibernate不可能為對應記錄的外來鍵列指定值;等到設定關聯關係時, Hibernate只能再次使用update語句來修改關聯關係。(自己可以看控制檯輸出的sql語句,所以不建議上述我的做法,但是這樣也是可以的,只是多了update 語句)
  3. 不要通過Person物件來設定關聯關係,因為我們已經在Person對映檔案的<set…/>元素中指定了inverse=“true”,這表明Person物件不能控制關聯關係。(這也是inverse的作用,inverse 也就只有在雙向關係,才發揮作用,只給某一方控制權,去維護關聯關。如果一方的對映檔案中設定為true,說明在對映關係中讓對方來維護關係。級聯的話控制權主要看,誰包含另外關聯物件的引用,誰維持關係,在雙向關係中,互相都有引用,那麼雙方都可以維護關係,開銷不就增加了,所以在雙向關係中,建議使用inverse
使用例子(基於連線表)

持久化類不變,修改兩邊的對映檔案如下:

 <set name="address"  inverse="true" table="person_address" >
 <key column="person_id"></key><!-- 對映持久化例項的主鍵作為外來鍵列 -->
 <many-to-many class="Address" unique="true"  column="address_id" ></many-to-many>
 <!-- 對映關聯持久化例項的主鍵作為外來鍵列  -->
 </set>
 <join table="person_address">
     <key column="address_id"></key>   <!--  對映持久化例項的主鍵,作為外來鍵列,作為該表的主鍵,因為設定inserve 控制
     權在address 所以address_id 為關聯表的主鍵-->
     <many-to-one class="Person" name="p"  column="person_id"></many-to-one><!--對映關聯持久化例項的主鍵,作為外來鍵列  -->
   </join>

連線表是以address_id作為主鍵,是因為address_id是唯一的,無需使用person_id 組合作為主鍵,因為都是參考的是主鍵,所以肯定不為空,可以作為主鍵。`

基於連線表的1 -N雙向關聯類似於N-N關聯。1的一端使用集合元素對映,然後在集合元素裡增加子元素,該子元素對映到關聯類。為保證該實體是1的一端,應該為元素增加unique= ="true"屬性。N的一端則使用元素來強制使用連線表。因此將使用<key…>子元素來對映連線表中外來鍵列,且使用來對映連線表中關聯實體的外來鍵列。

兩邊指定連線表的table屬性不能省略,且屬性值必須相同,否則關聯映射出錯。

對映檔案的兩邊都指定了兩個外來鍵列,一個是持久化類例項在連線表中的外來鍵列名,另一個是關聯持久化例項在連線表中的外來鍵列名。 一定要保證兩邊對映的外來鍵列名對應相同。

雙向N-N

先來說一下什麼是多對多的關係,舉個例子,老師和學生,老師有語文老師,數學老師,英語老師等等,學生可以是1班的學生也可以是2班的學生,對於每個學生而言,他有多個老師給他講課,而對於每一個老師而言,他要授課的學生也有很多,像這樣的情況就可以描述成多對多了。即兩個表之間,每一個表中的記錄都對應另一個表的部分或全部記錄的集合,這種情況就是多對多關係,而單向多對多與雙向多對多的不同在於單向只是一方的資料模型有另一方的資料集合屬性。

使用例子(基於連線表)

多對多的關係,只能是基於連線表。

在上述瞭解單向和雙向多對多的區別,就需要修改持久化類,在兩邊都互相定義集合物件:

Person.hbm.xml


    	private Set<Address> address = new HashSet<Address>();
       //自行新增set、get方法

Address.hbm.xml

	private Set<Person> persons = new java.util.HashSet<Person>();
        //自行提供set、get方法
<set name="address"  inverse="true" table="person_address" >
 <key column="person_id"></key><!-- 對映關聯表實體的外來鍵列 -->
 <many-to-many class="Address"   column="address_id" ></many-to-many>
 <set table="person_address" name="persons">
   <key column="address_id"></key>
   <many-to-many class="Person" column="person_id"></many-to-many>
   </set>
``

連線表是以兩個外來鍵作為聯合主鍵,是因為兩個外來鍵都不是唯一的,必須組合起來才是唯一,因為都是參考的是主鍵,所以肯定不為空,可以作為連線主鍵。`
   		   Person p = new Person();
	   p.setAge(20);
	   Person p1 = new Person();
	   p1.setAge(12);
	   ss.save(p);
	   ss.save(p1);
	   com.example.test.bean.Address a = new com.example.test.bean.Address();
	   Set<Person> set = new java.util.HashSet<Person>();
	   set.add(p1);
	   set.add(p);
	   a.setAddressdetail("大中路");
	   a.setPersons(set);
	   ss.save(a);

#### 雙向的1-1	

與單向1-1關聯類似的是,雙向1- 1關聯也有三種對映策略;基於主鍵、基於外來鍵、使用連線表。下面分別介紹這三種對映策略。

關係:一個人對應一個地址,一個地址屬於一個人。

##### 使用例子(基於外來鍵)

修改持久類,持有對方的引用,並提供set、get方法。
Person.hbm.xml

Address.hbm.xml
<many-to-one name="person" class="Person" unique="true" column="person_id"   ></many-to-one>

上面對映標籤,可以互相,主要的區別,就是你把外來鍵設定在哪張表。

上述的對映檔案,我主要用property-ref 這個有啥用,這個值是關聯持久化類的某個屬性,預設是關聯持久化類的主鍵。在查詢的時候,通常都是兩張表關聯查詢,肯定要通過某一個欄位,就是通過它設定的。

沒有使用該屬性,查詢語句列印:

![在這裡插入圖片描述](https://img-blog.csdn.net/20180923162924360?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ZlbnVzMzIx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)

使用該屬性,查詢語句列印:
![在這裡插入圖片描述](https://img-blog.csdn.net/2018092316300137?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ZlbnVzMzIx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)

兩者一比較,你就懂了。
	   Person p = new Person();
	   p.setAge(20);
	   ss.save(p);
	   com.example.test.bean.Address a1 = new com.example.test.bean.Address();
	   a1.setAddressdetail("大中路1");
	   ss.save(a1);
	   a1.setPerson(p);//設定關聯關係,同時給外來鍵列賦值,如果設定級聯關係,在這設定級聯關係,就不需要手動持久化Person,級聯會自動幫你處理。
	   Person p1 = ss.load(Person.class, 1);
	   System.out.println(p1.getAddress().getAddressdetail());

##### 使用例子(基於主鍵)

如果需要採用基於主鍵的對映策略,則一端的主鍵生成器需要使用foreign 策略,表明將根據對方的主鍵來生成自己的主鍵,本實體不能擁有自己的主鍵生成策略。當然,任意一端都可以採用foreign 主鍵生成器策略,表明將根據對方主鍵來生成自己的主鍵。使用foreign主鍵生成策略的實體將不能主動指定主鍵,必須由關聯實體的主鍵來生成自己的主鍵值。

在雙方持久化類中定義互相的引用,並修改對映檔案:

Person.hbm.xml:

<id name="id" type="integer"  column="person_id">
<generator class="foreign">
<param name="property">address</param><!--這裡填的值必須是持久化類的屬性,這裡必須是one to one 中的name 不能直接指定另外持久化類的屬性值,如果這樣,你並知道關聯的是哪個持久化類的例項 -->
</generator>
</id>
<one-to-one name="address"></one-to-one><!-- 指定關聯哪個持久化類,其中property-ref 預設是關聯持久化的主鍵 -->

Addres.hbm.xml:

<id name="addressid" column="address_id">
<generator class="identity" ></generator>
</id>
<property name="addressdetail" />
<one-to-one name="person"></one-to-one>
              Person p = new Person();
	   p.setAge(20);
	   com.example.test.bean.Address a1 = new com.example.test.bean.Address();
	   a1.setAddressdetail("大中路1");
	   p.setAddress(a1);// 如果沒有該句,報出attempted to assign id from null one-to-one property 
	   //必須設定該句,因為我們對映檔案的person的主鍵 是參看addres 的主鍵,這裡如果設定在Address 參看
	   //person的主鍵,則需要使用a1.setPerson(p)
	   ss.save(p);
	   ss.save(a1);

##### 使用例子(基於連線表)

持久化類不變,修改對映檔案:

Person.hbm.xml:


Address.hbm.xml:

```

我們可以看出上面兩個對映檔案的配置,是相互對應的,實際上只生成一張表,不要看配置了兩個。

帶連線表的雙向1-1關聯,兩端都需指定連線表的表名、外來鍵列的列名。兩個集合元素,如<onn…/>的table 元素的值必須指定,而且要相同。<join…/>元素的兩個子元素:<ke…>和<many-to-on…/>都必須指定column屬性,<ke…/>和<many-to-on…> 分別是指定本持久化類、關聯類在連線表中的外來鍵列名,因此兩邊的<ke…>與<many-to-one…>的column屬性交叉相同,也就是說,一-端的<join…>元素的<ke…/>的colomn值為a,<many-to-on…/>的column為b,則另一端的<join…>元素的<ke…/>的column值為b,<many-to-one…/>的column值為a.

		   Person p = new Person();
		   p.setAge(20);
		   com.example.test.bean.Address a1 = new com.example.test.bean.Address();
		   a1.setAddressdetail("大中路1");
		   a1.setPerson(p);//設定person和address 關聯關係
		   p.setAddress(a1);
		   ss.save(a1);
		   ss.save(p);

Tip

外來鍵:就是從表參照主表的中某一列,所以外來鍵都是在從表中。

   <one-to-one></one-to-one>
   <many-to-one></many-to-one>
   <one-to-many></one-to-many> 
   <many-to-many></many-to-many>
//在這三種標籤中,  <many-to-one>、<many-to-many> 還有集合是會產生外來鍵的。可以看出,外來鍵總是產生在多(N)方。外來鍵在表先有一個欄位,然後在指定這個欄位是參考哪個表的哪個列。 另外,外來鍵這個欄位,你需要在程式碼set、它會根據引用的持久化類的屬性值 去設定值,如果沒有指定級聯,這步需要你自己去操作。

cascade的值:

  • all: 所有情況下均進行關聯操作,即save-update和delete。
  • none: 所有情況下均不進行關聯操作。這是預設值。
  • save-update: 在執行save/update/saveOrUpdate時進行關聯操作。
  • delete: 在執行delete 時進行關聯操作。
  • all-delete-orphan: 當一個節點在物件圖中成為孤兒節點時,刪除該節點。

<many-to-one… ./>或<many-to-many…/>關係中指定級聯沒什麼意義。級聯通常在<one-to-one…/>和<one-to-many…/>關係中比較有用一 因為級聯操作應該是由主表記錄傳播到從表記錄, 而從表記錄則不應該傳播到主表記錄。    這個all-delete-orphan:什麼是孤兒節點,舉個例子,班級和學生,一張classes表,一張student表,student表中有5個學生的資料,其5個學生都屬於這個班級,也就是這5個學生中的外來鍵欄位都指向那個班級,現在刪除其中一個學生(remove),進行的資料操作僅僅是將student表中的該學生的外來鍵欄位置為null,但是班級對它的引用還在,也就是說,則個學生是沒有班級的,所以稱該學生為孤兒節點,我們本應該要將他完全刪除的,但是結果並不如我們所想的那樣,所以設定這個級聯屬性,就是為了刪除這個孤兒節點。也就是解決這類情況。

設定級聯 cascade 是在產生外來鍵的一方才可以設定,級聯幫我們做的,是關聯的表的持久化,它自動,不需要手動save(還有刪除)。

如果在配置對映檔案的時候,有時用連線表,一定要配置所有的屬性之後,否則會報錯。

我們建立在使用hibernate 配置對映關係的一定要給欄位設定column ,不要用預設,很容易出問題

optional:舉個例:某項訂單(Order)中沒有訂單項(OrderItem),如果optional 屬性設定為false,獲取該項訂單(Order)時,得到的結果將不顯示,如果optional屬性設定為true,仍然可以獲取該項訂單,但訂單中 指向訂單項的屬性值為null。

optional=false 聯接關係為inner join。 optional=true 聯接關係為left join。

這也是內連線和外連線的區別,就是對條件不符合的時候,處理時不一樣。

我們發現在使用對映關係,基於生成表的時候,如果集合標籤,直接加table 屬性,如果是其他的一律使用join標籤,裡面在進行相應的配置。

參考學習 參考學習 參考學習 參考學習 參考學習 參考學習