Hibernate之關係映射

關係映射

         注意:這裏的關係是指:對象之間的關係並不是指數據庫的關係,-----紅色重要

         存在以下關係:

1、    一對一

u       單向(主鍵、外鍵)

u       雙向(主鍵、外鍵)

2、    一對多

u       單向

u       雙向

3、    多對一

u       單向

u       雙向

4、    多對多

u       單向

u       雙向

5、    集合映射

u       List

u       Set

u       Map

6、    繼承關係(不重要)

u       單表

u       多表

u       一張主表、多張子表

7、    組件映射

u       @Embeddable

u       @Embedded


一、 一對一關聯映射

²        兩個對象之間是一對一的關係,如Person-IdCard(人—身份證號)

²        有兩種策略可以實現一對一的關聯映射

Ø        主鍵關聯:即讓兩個對象具有相同的主鍵值,以表明它們之間的一一對應的關係;數據庫表不會有額外的字段來維護它們之間的關係,僅通過表的主鍵來關聯。

Ø        唯一外鍵關聯:外鍵關聯,本來是用於多對一的配置,但是如果加上唯一的限制之後,也可以用來表示一對一關聯關係。

 

對象模型

實體類:

/** 人-實體類 */
public class Person {
    private int id;
    private String name;
    public int getId() {return id;  }
    public void setId(int id) {this.id = id;}
    public String getName() {return name;}
    public void setName(Stringname) {this.name = name;}
}
 
/**身份證-實體類*/
public class IdCard {
    private int id;
    private String cardNo;
    public int getId() {return id;}
    public void setId(int id) {this.id = id;}
    public String getCardNo(){ return cardNo;}
    public void setCardNo(StringcardNo) {this.cardNo = cardNo;}
}


(一) 唯一外鍵關聯-單向(unilateralism)

1、 說明:

人—-> 身份證號(PersonàIdCard),從IdCard看不到Person對象

2、 對象模型

需要在Person類中持有IdCard的一個引用idCard,則IdCard中沒有Person的引用


3、 關係模型

關係模型目的:是實體類映射到關係模型(數據庫中),是要求persion中添加一個外鍵指向idcard


4、 實體類:

      注:IdCard是被引用對象,沒有變化。

     

    /** 人-實體類 */
public class Person {
    private int id;
    private String name;
private IdCard idCard;//引用IdCard對象
    public int getId() {return id;  }
    public void setId(int id) {this.id = id;}
    public String getName() {return name;}
    public void setName(String name){this.name = name;}
public IdCard getIdCard() { return idCard;}
    public void setIdCard(IdCardidCard) {this.idCard = idCard;}
}

 

5、 xml映射

IdCard實體類的映射文件:

     因爲IdCard是被引用的,所以沒有什麼特殊的映射

<hibernate-mapping>
  <class name="com.wjt276.hibernate.IdCard" table="t_idcard">
      <id name="id" column="id">
          <generator class="native"/>
      </id>
      <property name="cardNo"/>
  </class>
</hibernate-mapping>


Person實體類的映射文件

         在映射時需要添加一個外鍵的映射,就是指定IdCard的引用的映射。這樣映射到數據庫時,就會自動添加一個字段並作用外鍵指向被引用的表

<hibernate-mapping>
  <class name="com.wjt276.hibernate.Person" table="t_person">
      <id name="id" column="id">
          <generator class="native"/>
      </id>
      <property name="name"/> 


<!-- <many-to-one>:在多的一端(當前Person一端),加入一個外鍵(當前爲idCard)指向一的一端(當前IdCard),但多對一 關聯映射字段是可以重複的,所以需要加入一個唯一條件unique="true",這樣就可以此字段唯一了。-->

 

<many-to-one name="idCard" unique="true"/>
  </class>
</hibernate-mapping>

注意:這裏的<many-to-one>標籤中的name屬性值並不是數據庫中的字段名,而是Person實體類中引用IdCard對象成員屬性的getxxx方法後面的xxx(此處是getIdCard,所以是idCard),要求第一個字段小寫。如果不指定column屬性,則數據庫中的字段名同name值

6、 annotateon註解映射

注意IdCard是被引用對象,除正常註解,無需要其它註解

/**身份證*/
@Entity
public class IdCard {
  private int id;
  private String cardNo;
  @Id
  @GeneratedValue
  public int getId() {return id;}
  public void setId(int id) { this.id = id;}
  public String getCardNo(){return cardNo;}
  public void setCardNo(StringcardNo) {this.cardNo = cardNo;}
}
 


而引用對象的實體類需要使用@OneToOne進行註解,來表面是一對一的關係

再使用@JoinColumn註解來爲數據庫表中這個外鍵指定個字段名稱就可以了。如果省略@JoinColumn註解,則hibernate會自動爲其生成一個字段名(好像是:被引用對象名稱_被引用對象的主鍵ID)

/** 人-實體類 */
@Entity
public class Person {
  private int id;
  private IdCard idCard;//引用IdCard對象  
  private String name;   
  @Id
  @GeneratedValue
  public int getId() {return id;}
  @OneToOne//表示一對一的關係
  @JoinColumn(name="idCard")//爲數據中的外鍵指定個名稱
  public IdCard getIdCard(){ return idCard;}
  public String getName() {return name;}
  public void setId(int id) {this.id = id;}
  public void setIdCard(IdCardidCard) {this.idCard = idCard;}
  public void setName(Stringname) {this.name = name;}   
}


7、 生成的SQL語句:

    

create tableIdCard (
        id integernot null auto_increment,
        cardNo varchar(255),
        primary key(id)
    )
    create tablePerson (
        id integernot null auto_increment,
        namevarchar(255),
        idCardinteger,//新添加的外鍵
        primary key(id)
    )
    alter tablePerson
        add indexFK8E488775BE010483 (idCard),
        addconstraint FK8E488775BE010483
        foreign key(idCard) //外鍵
        referencesIdCard (id)//引用IdCard的id字段



8、 存儲測試

Session session = sf.getCurrentSession();      
IdCard idCard = new IdCard();
idCard.setCardNo("88888888888888888888888");       
session.beginTransaction();
// 如果先不保存idCard,則出拋出Transient異常,因爲idCard不是持久化狀態。
session.save(idCard);      
Person person = new Person();
person.setName("菜10");
person.setIdCard(idCard);
session.save(person);
session.getTransaction().commit();
 


(二) 唯一外鍵關聯-雙向

1、 說明:

人<—-> 身份證號(Person<->IdCard)雙向:互相持有對方的引用

2、 對象模型:



3、 關係模型:

關係模型沒有任務變化,同單向

4、 實體類:

實體類,只是相互持有對象的引用,並且要求getter和setter方法

5、 xml映射

Person實體類映射文件:同單向的沒有變化

IdCard實體類映射文件:如果使用同樣的方法映射,這樣就會在表中也添加一個外鍵指向對象,但對象已經有一個外鍵指向自己了,這樣就造成了庸字段,因爲不需要在表另外添加字段,而是讓hibernate在加載這個對象時,會根據對象的ID到對方的表中查詢外鍵等於這個ID的記錄,這樣就把對象加載上來了。也同樣需要使用<one-to-one>標籤來映射,但是需要使用property-ref屬性來指定對象持有你自己的引用的成員屬性名稱(是gettxxxx後面的名稱),這樣在生成數據庫表時,就不會再添加一個多於的字段了。數據加載時hibernate會根據這些配置自己加載數據

  

  <class name="com.wjt276.hibernate.IdCard" table="idcard">
        <id name="id" column="id">
            <generator class="native"/></id>
        <property name="cardNo"/>
        <!--<one-to-one>標籤:告訴hibernate如何加載其關聯對象
            property-ref屬性:是根據哪個字段進行比較加載數據 -->
        <one-to-one name="person" property-ref="idCard"/>
    </class>

一對一 唯一外鍵 關聯映射 雙向 需要在另一端(當前IdCard),添加<one-to-one>標籤,指示hibernate如何加載其關聯對象(或引用對象),默認根據主鍵加載(加載person),外鍵關聯映射中,因爲兩個實體採用的是person的外鍵來維護的關係,所以不能指定主鍵加載person,而要根據person的外鍵加載,所以採用如下映射方式:

    <!--<one-to-one>標籤:告訴hibernate如何加載其關聯對象

            property-ref屬性:是根據哪個字段進行比較加載數據 -->

       

 <one-to-one name="person" property-ref="idCard"/>

6、 annotateon註解映射

Person註解映射同單向一樣

IdCard註解映射如下:使用@OneToOne註解來一對一,但這樣會在表中多加一個字段,因爲需要使用對象的外鍵來加載數據,所以使用屬性mappedBy屬性在實現這個功能

@Entity
public class IdCard {
  private int id;
  private String cardNo;
  private Person person; 
  //mappedBy:在指定當前對象在被Person對象的idCard做了映射了
  //此值:當前對象持有引用對象中引用當前對象的成員屬性名稱(getXXX後的名稱)
  //因爲Person對象的持有IdCard對象的方法是getIdCard()因爲需要小寫,所以爲idCard
  @OneToOne(mappedBy="idCard")
  public Person getPerson(){return person;}
  public void setPerson(Person person){this.person = person;}
  @Id
  @GeneratedValue
  public int getId() {return id;}
  public void setId(int id) { this.id = id;}
  public String getCardNo(){return cardNo;}
  public void setCardNo(StringcardNo) {this.cardNo = cardNo;}
}


7、 生成SQL語句

因爲關係模型沒有變化,也就是數據庫的結構沒有變化,只是在數據加載時需要相互加載對方,這由hibernate來完成。因爲生成的sql語句同單向一樣

8、 存儲測試

存儲同單向一樣

9、 總結:

規律:凡是雙向關聯,必設mappedBy

 

(三) 主鍵關聯-單向(不重要)

主鍵關聯:即讓兩個對象具有相同的主鍵值,以表明它們之間的一一對應的關係;數據庫表不會有額外的字段來維護它們之間的關係,僅通過表的主鍵來關聯。

 

1、 說明:

人—-> 身份證號(PersonàIdCard),從IdCard看不到Person對象

2、 對象模型

站在人的角度來看,對象模型與唯一外鍵關聯一個,只是關係模型不同


3、 關係模型

因爲是person引用idcard,所以idcard要求先有值。而person的主鍵值不是自己生成的。而是參考idcard的值,person表中即是主鍵,同時也是外鍵


4、 實體類:

實體類同 一對一 唯一外鍵關聯的實體類一個,在person對象中持有idcard對象的引用(代碼見唯一外鍵關係)

5、 xml映射

IdCard映射文件,先生成ID

    

<class name="com.wjt276.hibernate.IdCard" table="t_idcard">
        <id name="id"column="id">
            <generator class="native"/>
        </id>
        <property name="cardNo"/>
    </class>

 

Person實體類映射文件,ID是根據IdCard主鍵值   

<class name="com.wjt276.hibernate.Person"table="t_person">
        <id name="id"column="id">
<!--因爲主鍵不是自己生成的,而是作爲一個外鍵(來源於其它值),所以使用foreign生成策略
foreign:使用另外一個相關聯的對象的標識符,通常和<one-to-one>聯合起來使用。再使用元素<param>的屬性值指定相關聯對象(這裏Person相關聯的對象爲idCard,則標識符爲idCard的id)爲了能夠在加載person數據同時加載IdCard數據,所以需要使用一個標籤<one-to-one>來設置這個功能。  -->
            <generator class="foreign">
                <!-- 元素<param>屬性name的值是固定爲property -->
                <param name="property">idCard</param>
            </generator>
        </id>
        <property name="name"/>
        <!-- <one-to-one>標籤
        表示如何加載它的引用對象(這裏引用對象就指idCard這裏的name值是idCard),同時也說是一對一的關係。 默認方式是根據主鍵加載(把person中的主鍵取出再到IdCard中來取相關IdCard數據。) 我們也說過此主鍵也作爲一個外鍵引用 了IdCard,所以需要加一個數據庫限制(外鍵約束)constrained="true"     -->
        <one-to-one name="idCard"constrained="true"/> 


6、 annotateon註解映射

Person實體類註解

方法:只需要使用@OneToOne註解一對一關係,再使用@PrimaryKeyJoinColumn來註解主鍵關係映射。

@Entity
public class Person {
    private int id;
    private IdCard idCard;//引用IdCard對象  
    private String name;   
    @Id
    public int getId() {return id;}
    @OneToOne//表示一對一的關係
    @PrimaryKeyJoinColumn//註解主鍵關聯映射
    public IdCard getIdCard(){ return idCard;}
    public String getName() {return name;}
    public void setId(int id) {this.id = id;}
    public void setIdCard(IdCard idCard){this.idCard = idCard;}
    public void setName(Stringname) {this.name = name;}   
}
 

IdCard實體類,不需要持有對象的引用,正常註解就可以了。

7、 生成SQL語句

生成的兩個表並沒有多餘的字段,因爲是通過主鍵在關鍵的

   

 create tableIdCard (
        id integernot null auto_increment,
        cardNovarchar(255),
        primary key (id)
    )
    create tablePerson (
        id integernot null,
        namevarchar(255),
        primary key(id)
  )
alter table person
add index FK785BED805248EF3 (id),
add constraint FK785BED805248EF3
foreign key (id) references idcard (id)

    注意:annotation註解後,並沒有映射出外鍵關鍵的關聯,而xml可以映射,是主鍵關聯不重要

8、 存儲測試

session = HibernateUtils.getSession();
tx = session.beginTransaction();
IdCard idCard = new IdCard();
idCard.setCardNo("88888888888888888888888");
           
Person person = new Person();
person.setName("菜10");
person.setIdCard(idCard);
           
//不會出現TransientObjectException異常
//因爲一對一主鍵關鍵映射中,默認了cascade屬性。
session.save(person);
tx.commit();
 

9、 總結

讓兩個實體對象的ID保持相同,這樣可以避免多餘的字段被創建

<id name="id"column="id">
      <!—person的主鍵來源idcard,也就是共享idCard的主鍵-->
          <generator class="foreign">
              <param name="property">idCard</param>
          </generator>
      </id>
      <property name="name"/>
<!—one-to-one標籤的含義:指示hibernate怎麼加載它的關聯對象,默認根據主鍵加載
  constrained="true",表面當前主鍵上存在一個約束:person的主鍵作爲外鍵參照了idCard-->
      <one-to-one name="idCard" constrained="true"/>


(四) 主鍵關聯-雙向(不重要)

主鍵關聯:即讓兩個對象具有相同的主鍵值,以表明它們之間的一一對應的關係;數據庫表不會有額外的字段來維護它們之間的關係,僅通過表的主鍵來關聯。

主鍵關聯映射,實際是數據庫的存儲結構並沒有變化,只是要求雙方都可以持有對象引用,也就是說實體模型變化,實體類都相互持有對方引用。

另外映射文件也變化了。


1、 xml映射

Person實體類映射文件不變,

IdCard如下:

<class name="com.wjt276.hibernate.IdCard" table="t_idcard">
        <id name="id" column="id">
            <generator class="native"/> </id>
        <property name="cardNo"/>
<!—one-to-one標籤的含義:指示hibernate怎麼加載它的關聯對象(這裏的關聯對象爲person),默認根據主鍵加載-->
        <one-to-one name="person"/>
    </class>

2、 annotateon註解映射:

Person的註解不變,同主鍵單向註解

 

IdCard註解,只需要在持有對象引用的getXXX前加上

        @OneToOne(mappedBy="idCard") 如下:

@Entity
public class IdCard {
    private int id;
    private String cardNo;
    private Person person; 
    @OneToOne(mappedBy="idCard")
    public Person getPerson(){
        return person;
    }}


(五) 聯合主鍵關聯(Annotation方式)

實現上聯合主鍵的原理同唯一外鍵關聯-單向一樣,只是使用的是@JoinColumns,而不是@JoinColumn,實體類註解如下:

    

@OneToOne
    @JoinColumns(
        {
            @JoinColumn(name="wifeId", referencedColumnName="id"),
            @JoinColumn(name="wifeName", referencedColumnName="name")
        }
    )
    public WifegetWife() {
        return wife;
    }

        注意:@oinColumns註解聯合主鍵一對一聯繫,然後再使用@JoinColumn來註解當前表中的外鍵字段名,並指定關聯哪個字段,使用referencedColumnName指定哪個字段的名稱

 

二、 component(組件)關聯映射

(一) Component關聯映射:

目前有兩個類如下:


         大家發現用戶與員工存在很多相同的字段,但是兩者有不可以是同一個類中,這樣在實體類中每次都要輸入很多信息,現在把聯繫信息抽取出來成爲一個類,然後在用戶、員工對象中引用就可以,如下:


值對象沒有標識,而實體對象具有標識,值對象屬於某一個實體,使用它重複使用率提升,而且更清析。

 

以上關係的映射稱爲component(組件)關聯映射

在hibernate中,component是某個實體的邏輯組成部分,它與實體的根本區別是沒有oid,component可以成爲是值對象(DDD)。

採用component映射的好處:它實現了對象模型的細粒度劃分,層次會更加分明,複用率會更高。

(二) User實體類:

public class User {
private int id;
  private String name;   
  private Contact contact;//值對象的引用   
  public int getId() {return id;}
  public void setId(int id) { this.id = id;}
  public String getName() {   return name;}
  public void setName(Stringname) {  this.name = name;}
  public ContactgetContact() {   return contact;}
  public void setContact(Contactcontact) {   this.contact = contact;}   
}


(三) Contact值對象:

public class Contact {
  private String email;  
  private String address;
  private String zipCode;
  private String contactTel;
  public String getEmail(){  return email;}
  public void setEmail(Stringemail) {    this.email = email; }
  public StringgetAddress() {return address;}
  public void setAddress(Stringaddress) {this.address = address;}
  public StringgetZipCode() {return zipCode;}
  public void setZipCode(StringzipCode) {this.zipCode = zipCode;}
  public StringgetContactTel() { return contactTel;}
  public voidsetContactTel(String contactTel){this.contactTel = contactTel;}
}
 


(四) xml--User映射文件(組件映射):

<hibernate-mapping>
  <class name="com.wjt276.hibernate.User" table="t_user">
      <id name="id" column="id">
          <generator class="native"/>
      </id>
      <property name="name" column="name"/>
      <!-- <component>標籤用於映射Component(組件)關係
          其內部屬性正常映射。
       -->
      <component name="contact">
          <property name="email"/>
          <property name="address"/>
          <property name="zipCode"/>
          <property name="contactTel"/>          
      </component>
  </class>
</hibernate-mapping>


(五) annotateon註解

使用@Embedded用於註解組件映射,表示嵌入對象的映射

@Entity
public class User {
    private int id;
    private String name;   
    private Contact contact;//值對象的引用   
    @Id
    @GeneratedValue
    public int getId() {    return id;}
 
    @Embedded//用於註解組件映射,表示嵌入對象的映射
    public ContactgetContact() {return contact;}
    public void setContact(Contactcontact) {this.contact = contact;}  


Contact類是值對象,不是實體對象,是屬於實體類的某一部分,因此沒有映射文件

(六) 導出數據庫輸出SQL語句:

  

  create table User (
        id integer not null auto_increment,
        address varchar(255),
        contactTel varchar(255),
        email varchar(255),
        zipCode varchar(255),
        name varchar(255),
        primary key (id)
)

(七) 數據表結構:


注:雖然實體類沒有基本聯繫信息,只是有一個引用,但在映射數據庫時全部都映射進來了。以後值對象可以重複使用,只要在相應的實體類中加入一個引用即可。

(八) 組件映射數據保存:

          

 session =HibernateUtils.getSession();
           tx =session.beginTransaction();
 
           User user= new User();
           user.setName("10");
          
           Contactcontact = new Contact();
           contact.setEmail("wjt276");
           contact.setAddress("aksdfj");
           contact.setZipCode("230051");
           contact.setContactTel("3464661");
          
           user.setContact(contact);
           session.save(user);
          
           tx.commit();

實體類中引用值對象時,不用先保存值對象,因爲它不是實體類,它只是一個附屬類,而session.save()中保存的對象是實體類。

 

三、       多對一 –單向

場景:用戶和組;從用戶角度來,多個用戶屬於一個組(多對一 關聯)

使用hibernate開發的思路:先建立對象模型(領域模型),把實體抽取出來。

目前兩個實體:用戶和組兩個實體,多個用戶屬於一個組,那麼一個用戶都會對應於一個組,所以用戶實體中應該有一個持有組的引用。

(一) 對象模型圖:



(二) 關係模型:

 
 

 


(三) 關聯映射的本質:

    將關聯關係映射到數據庫,所謂的關聯關係是對象模型在內存中一個或多個引用。

(四) 實體類

User實體類:

public class User {
  private int id;
  private String name;
  private Group group;
  public Group getGroup() {return group;  }
public void setGroup(Group group) {this.group = group;}
  public int getId() {return id;  }
  public void setId(int id) { this.id = id;}
  public String getName() {return name;}
  public void setName(Stringname) {  this.name = name;}}
Group實體類:
public class Group {
  private int id;
  private String name;
  public int getId() {return id;}
  public void setId(int id) { this.id = id;}
  public String getName() {return name;}
  public void setName(Stringname) {this.name = name;}
}


實體類建立完後,開始創建映射文件,先建立簡單的映射文件:

(五) xml方式:映射文件:

1、 Group實體類的映射文件:

<hibernate-mapping>
  <class name="com.wjt276.hibernate.Group" table="t_group">
      <id name="id" column="id">
          <generator class="native"/>
      </id>
      <property name="name"/>
  </class>
</hibernate-mapping>


2、 User實體類的映射文件:

<hibernate-mapping>
  <class name="com.wjt276.hibernate.User" table="t_user">
      <id name="id" column="id">
          <generator class="native"/>
      </id>
      <property name="name"/>
      <!--<many-to-one> 關聯映射 多對一的關係
  name:是維護的屬性(User.group),這樣表示在多的一端表裏加入一個字段名稱爲group,
但group與SQL中的關鍵字重複,所以需要重新命名字段(column="groupid").這樣這個字段(groupid)會作爲外鍵參照數據庫中group表(t_group也叫一的一端),也就是就在多的一 端加入一個外鍵指向一的一端。 -->
      <many-to-one name="group" column="groupid"/>
  </class>
</hibernate-mapping>


3、 ※<many-to-one>標籤※:

例如:<many-to-one name="group" column="groupid"/>

<many-to-one> 關聯映射 多對一的關係

name:是維護的屬性(User.group),這樣表示在多的一端表裏加入一個字段名稱爲group,但group與SQL中的關鍵字重複,所以需要重新命名字段(column="groupid").這樣這個字段(groupid)會作爲外鍵參照數據庫中group表(t_group也叫一的一端),也就是就在多的一端加入一個外鍵指向一的一端。

 

         這樣導出至數據庫會生成下列語句:

alter table t_user drop foreign keyFKCB63CCB695B3B5AC
drop table if exists t_group
drop table if exists t_user
create table t_group (id integer not nullauto_increment, name varchar(255), primary key (id))
create table t_user (id integer not nullauto_increment, name varchar(255), groupid integer,primary key (id))
alter table t_user add index FKCB63CCB695B3B5AC (groupid), add constraint FKCB63CCB695B3B5AC foreign key (groupid) referencest_group (id)
 


(六) annotation

Group(一的一端)註解只需要正常的註解就可以了,因爲在實體類中它是被引用的。

     User*(多的一端):@ManyToOne來註解多一對的關鍵,並且用@JoinColumn來指定外鍵的字段名

@Entity
public class User {
  private int id;
  private String name;
  private Group group;
 
  @ManyToOne
  @JoinColumn(name="groupId")
  public Group getGroup() {
      return group;
  }


(七) 多對一 存儲(先存儲group(對象持久化狀態後,再保存user)):

session = HibernateUtils.getSession();
            tx =session.beginTransaction();
 
            Groupgroup = new Group();
            group.setName("wjt276");
            session.save(group); //存儲Group對象。
           
            Useruser1 = new User();
            user1.setName("菜10");
            user1.setGroup(group);//設置用戶所屬的組
           
            Useruser2 = new User();
            user2.setName("容祖兒");
            user2.setGroup(group);//設置用戶所屬的組
           
            //開始存儲
            session.save(user1);//存儲用戶
            session.save(user2);
                       
            tx.commit();//提交事務


執行後hibernate執行以下SQL語句:

Hibernate: insert into t_group (name) values (?)
Hibernate: insert into t_user (name, groupid) values (?, ?)
Hibernate: insert into t_user (name, groupid) values (?, ?)

注意:如果上面的session.save(group)不執行,則存儲不存儲不成功。則拋出TransientObjectException異常。

因爲Group爲Transient狀,Object的id沒有分配值。2610644

 

結果:persistent狀態的對象是不能引用Transient狀態的對象

 

以上代碼操作,必須首先保存group對象,再保存user對象。我們可以利用cascade(級聯)方式,不需要先保存group對象。而是直接保存user對象,這樣就可以在存儲user之前先把group存儲了。

    利用cascade屬性是解決TransientObjectException異常的一種手段。

(八) 重要屬性-cascade(級聯):

    級聯的意思是指定兩個對象之間的操作聯運關係,對一個 對象執行了操作之後,對其指定的級聯對象也需要執行相同的操作,取值:all、none、save_update、delete

1、    all:代碼在所有的情況下都執行級聯操作

2、    none:在所有情況下都不執行級聯操作

3、    save-update:在保存和更新的時候執行級聯操作

4、    delete:在刪除的時候執行級聯操作。

 

1、 xml

        例如:

<many-to-one name="group"column="groupid" cascade="save-update"/>

2、 annotation

cascade屬性:其值:  CascadeType.ALL                       所有

CascadeType.MERGE                save+ update

CascadeType.PERSIST              

CascadeType.REFRESH

CascadeType.REMOVE

         例如:

                   @ManyToMany(cascade={CascadeType.ALL})

 

注意:cascade只是幫我們省了編程的麻煩而已,不要把它的作用看的太大

(九) 多對一  加載數據

代碼如下:

session = HibernateUtils.getSession();

            tx =session.beginTransaction();

           

            Useruser = (User)session.load(User.class, 3);

            System.out.println("user.name=" + user.getName());

            System.out.println("user.group.name=" + user.getGroup().getName());

       

            //提交事務

            tx.commit();

執行後向SQL發出以下語句:

Hibernate: select user0_.id as id0_0_,user0_.name as name0_0_, user0_.groupid as groupid0_0_ from t_user user0_ whereuser0_.id=?

Hibernate: select group0_.id as id1_0_,group0_.name as name1_0_ from t_group group0_ where group0_.id=?

 

可以加載Group信息:因爲採用了<many-to-one>這個標籤,這個標籤會在多的一端(User)加一個外鍵,指向一的一端(Group),也就是它維護了從多到一的這種關係,多指向一的關係。當你加載多一端的數據時,它就能把一的這一端數據加載上來。當加載User對象後hibernate會根據User對象中的groupid再來加載Group信息給User對象中的group屬性。

 

四、       一對多 - 單向

在對象模型中,一對多的關聯關係,使用集合來表示。

實例場景:班級對學生;Classes(班級)和Student(學生)之間是一對多的關係。

(一) 對象模型:


(二) 關係模型:

 

 


一對多關聯映射利用了多對一關聯映射原理。

(三) 多對一、一對多的區別:

多對一關聯映射:在多的一端加入一個外鍵指向一的一端,它維護的關係是多指向一的。

一對多關聯映射:在多的一端加入一個外鍵指向一的一端,它維護的關係是一指向多的。

兩者使用的策略是一樣的,只是各自所站的角度不同。

(四) 實體類

Classes實體類:

public class Classes {

  private int id;

  private String name;

  //一對多通常使用Set來映射,Set是不可重複內容。

//注意使用Set這個接口,不要使用HashSet,因爲hibernate有延遲加載,

  private Set<Student>students = new HashSet<Student>();

  public int getId() {return id;  }

  public void setId(int id) {this.id = id;}

  public String getName() {return name;}

  public void setName(Stringname) {this.name = name;}

}

Student實體類:

public class Student {

  private int id;

  private String name;

  public int getId() {return id;}

  public void setId(int id) { this.id = id;}

  public String getName() {return name;}

  public void setName(Stringname) {  this.name = name;}

}

(五) xml方式:映射

1、 Student映射文件:

<hibernate-mapping>
    <class name="com.wjt276.hibernate.Student" table="t_student">
        <id name="id" column="id">
            <generator class="native"/>
        </id>
        <property name="name" column="name"/>
    </class>
</hibernate-mapping>


2、 Classes映射文件:

<hibernate-mapping>
    <class name="com.wjt276.hibernate.Classes" table="t_classess">
        <id name="id" column="id">
            <generator class="native"/>
        </id>
        <property name="name" column="name"/>      
        <!--<set>標籤 映射一對多(映射set集合),name="屬性集合名稱",然後在用<key>標籤,在多的一端加入一個外鍵(column屬性指定列名稱)指向一的一端,再採用<one-to-many>標籤說明一對多,還指定<set>標籤中name="students"這個集合中的類型要使用完整的類路徑(例如:class="com.wjt276.hibernate.Student") -->
        <set name="students">
            <key column="classesid"/>
            <one-to-many class="com.wjt276.hibernate.Student"/>
        </set>
    </class>
</hibernate-mapping>


(六) annotateon註解

一對多 多的一端只需要正常註解就可以了。

需要在一的一端進行註解一對多的關係。

使用@OneToMany

@Entity
public class Classes {
    private int id;
    private String name;
   
    // 一對多通常使用Set來映射,Set是不可重複內容。
    // 注意使用Set這個接口,不要使用HashSet,因爲hibernate有延遲加載,
    private Set<Student>students = new HashSet<Student>();
    @OneToMany//進行註解爲一對多的關係
    @JoinColumn(name="classesId")//在多的一端註解一個字段(名爲classessid)
    public Set<Student>getStudents() {
        return students;
}
 


(七) 導出至數據庫(hbmàddl)生成的SQL語句:

create table t_classes (id integer not null auto_increment, namevarchar(255), primary key (id))
create table t_student (id integer not null auto_increment, namevarchar(255), classesid integer, primary key (id))
alter table t_student add index FK4B90757070CFE27A (classesid), add constraint FK4B90757070CFE27A foreign key (classesid) referencest_classes (id)


(八) 一對多 單向存儲實例:

           

  session = HibernateUtils.getSession();
            tx =session.beginTransaction();
   
            Studentstudent1 = new Student();
            student1.setName("10");
            session.save(student1);//必需先存儲,否則在保存classess時出錯.
           
            Studentstudent2 = new Student();
            student2.setName("祖兒");
            session.save(student2);//必需先存儲,否則在保存classess時出錯.
           
            Set<Student>students = new HashSet<Student>();
            students.add(student1);
            students.add(student2);
           
            Classesclasses = new Classes();
            classes.setName("wjt276");
            classes.setStudents(students);
           
            session.save(classes);
            tx.commit();

(九) 生成的SQL語句:

Hibernate: insert into t_student (name) values (?)

Hibernate: insert into t_student (name) values (?)

Hibernate: insert into t_classes (name) values (?)

Hibernate: update t_student set classesid=? where id=?

Hibernate: update t_student set classesid=? where id=?

 

(十) 一對多,在一的一端維護關係的缺點:

    因爲是在一的一端維護關係,這樣會發出多餘的更新語句,這樣在批量數據時,效率不高。

    還有一個,當在多的一端的那個外鍵設置爲非空時,則在添加多的一端數據時會發生錯誤,數據存儲不成功。

(十一)       一對多 單向數據加載:

session = HibernateUtils.getSession();
          tx =session.beginTransaction();
          Classesclasses = (Classes)session.load(Classes.class, 2);
          System.out.println("classes.name=" + classes.getName());
          Set<Student> students = classes.getStudents();
          for(Iterator<Student> iter = students.iterator();iter.hasNext();){
              Studentstudent = iter.next();
              System.out.println(student.getName());
          }
          tx.commit();

(十二)       加載生成SQL語句:

Hibernate: select classes0_.id as id0_0_, classes0_.name as name0_0_ fromt_classes classes0_ where classes0_.id=?

Hibernate: select students0_.classesid as classesid1_, students0_.id asid1_, students0_.id as id1_0_, students0_.name as name1_0_ from t_studentstudents0_ where students0_.classesid=?

 

五、       一對多 - 雙向

         是加載學生時,能夠把班級加載上來。當然加載班級也可以把學生加載上來

1、  在學生對象模型中,要持有班級的引用,並修改學生映射文件就可以了。。

2、  存儲沒有變化

3、  關係模型也沒有變化

(一) xml方式:映射

學生映射文件修改後的:

  

<class name="com.wjt276.hibernate.Student" table="t_student">
      <id name="id" column="id">
          <generator class="native"/>
      </id>
      <property name="name" column="name"/>
<!--使用多對一標籤映射 一對多雙向,下列的column值必需與多的一端的key字段值一樣。-->
      <many-to-one name="classes" column="classesid"/>
  </class>

 

如果在一對多的映射關係中採用一的一端來維護關係的話會存在以下兩個缺點:①如果多的一端那個外鍵設置爲非空時,則多的一端就存不進數據;②會發出多於的Update語句,這樣會影響效率。所以常用對於一對多的映射關係我們在多的一端維護關係,並讓多的一端維護關係失效(見下面屬性)。

代碼:

  

 <class name="com.wjt276.hibernate.Classes" table="t_classes">
      <id name="id"column="id">
          <generator class="native"/>
      </id>
      <property name="name"column="name"/>
     
      <!--
          <set>標籤 映射一對多(映射set集合),name="屬性集合名稱"
          然後在用<key>標籤,在多的一端加入一個外鍵(column屬性指定列名稱)指向一的一端
          再採用<one-to-many>標籤說明一對多,還指定<set>標籤中name="students"這個集合中的類型
          要使用完整的類路徑(例如:class="com.wjt276.hibernate.Student")
inverse="false":一的一端維護關係失效(反轉) :false:可以從一的一端維護關係(默認);true:從一的一端維護關係失效,這樣如果在一的一端維護關係則不會發出Update語句。 -->
      <set name="students"inverse="true">
      <key column="classesid"/>
          <one-to-many class="com.wjt276.hibernate.Student"/>
      </set>
  </class>
 

(二) annotateon方式註解

首先需要在多的一端持有一的一端的引用

因爲一對多的雙向關聯,就是多對一的關係,我們一般在多的一端來維護關係,這樣我們需要在多的一端實體類進行映射多對一,並且設置一個字段,而在一的一端只需要進行映射一對多的關係就可以了,如下:

多的一端

@Entity
public class Student {
    private int id;
    private String name;
    private Classes classes;
   
    @ManyToOne
    @JoinColumn(name="classesId")
    public ClassesgetClasses() {
        return classes;
    }


一的一端

@Entity
public class Classes {
    private int id;
    private String name;
    // 一對多通常使用Set來映射,Set是不可重複內容。
    // 注意使用Set這個接口,不要使用HashSet,因爲hibernate有延遲加載,
    private Set<Student>students = new HashSet<Student>();
 
    @OneToMany(mappedBy="classes")//進行註解爲一對多的關係
    public Set<Student>getStudents() {
        return students;
}


(三) 數據保存:

一對多 數據保存,從多的一端進行保存:

session = HibernateUtils.getSession();
          tx =session.beginTransaction();
 
          Classesclasses = new Classes();
          classes.setName("wjt168");
          session.save(classes);
         
          Studentstudent1 = new Student();
          student1.setName("10");
          student1.setClasses(classes);
          session.save(student1);
         
          Studentstudent2 = new Student();
          student2.setName("祖兒");
          student2.setClasses(classes);
          session.save(student2);
         
          //提交事務
          tx.commit();

注意:一對多,從多的一端保存數據比從一的一端保存數據要快,因爲從一的一端保存數據時,會多更新多的一端的一個外鍵(是指定一的一端的。)

(四) 關於inverse屬性:

inverse主要用在一對多和多對多雙向關聯上,inverse可以被設置到集合標籤<set>上,默認inverse爲false,所以我們可以從一的一端和多的一端維護關聯關係,如果設置inverse爲true,則我們只能從多的一端維護關聯關係。

    注意:inverse屬性,隻影響數據的存儲,也就是持久化

(五) Inverse和cascade區別:

    Inverse是關聯關係的控制方向

    Casecade操作上的連鎖反應

(六) 一對多雙向關聯映射總結:

    在一的一端的集合上使用<key>,在對方表中加入一個外鍵指向一的一端

    在多的一端採用<many-to-one>

注意:<key>標籤指定的外鍵字段必須和<many-to-one>指定的外鍵字段一致,否則引用字段的錯誤

如果在一的一端維護一對多的關係,hibernate會發出多餘的update語句,所以我們一般在多的一端來維護關係。

 

六、       多對多 - 單向

Ø        一般的設計中,多對多關聯映射,需要一箇中間表

Ø        Hibernate會自動生成中間表

Ø        Hibernate使用many-to-many標籤來表示多對多的關聯

Ø        多對多的關聯映射,在實體類中,跟一對多一樣,也是用集合來表示的。

(一) 實例場景:

用戶與他的角色(一個用戶擁有多個角色,一個角色還可以屬於多個用戶)

(二) 對象模型:



(三) 關係模型:

 
 
 

(四) 實體類

Role實體類:

public class Role {
    private int id;
    private String name;
    public int getId() return id;}
    public void setId(int id) {this.id = id;}
    public String getName() {return name;}
    public void setName(Stringname) {  this.name = name;
   }
}


User實體類:

public class User {
    private int id;
    private String name;   
    private Set<User> roles = newHashSet<User>();// Role對象的集合
    public int getId() {return id;}
    public void setId(int id) {this.id = id;}
    public String getName() {return name;}
    public void setName(Stringname) {this.name = name;}
    public Set<User>getRoles() {return roles;}
    public voidsetRoles(Set<User> roles) {this.roles = roles;  }
}


(五) xml方式:映射

Role映射文件:

<class name="com.wjt276.hibernate.Role" table="t_role">
        <id name="id">
            <generator class="native"/>
        </id>
        <property name="name" column="name"/>
    </class>
User映射文件:
    <class name="com.wjt276.hibernate.User" table="t_user">
        <id name="id"column="id">
            <generator class="native"/>
        </id>
        <property name="name"/>
<!--使用<set>標籤映射集合(set),標籤中的name值爲對象屬性名(集合roles),而使用table屬性是用於生成第三方表名稱,例:table="t_user_role",但是第三方面中的字段是自動加入的,作爲外鍵分別指向其它表。
所以表<key>標籤設置,例:<key column="userid"/>,意思是:在第三方表(t_user_role)中加入一個外鍵並且指向當前的映射實體類所對應的表(t_user).使用<many-to-many>來指定此映射集合所對象的類(實例類),並且使用column屬性加入一個外鍵指向Role實體類所對應的表(t_role) -->
        <setname="roles" table="t_user_role">
            <key column="userid"/>
            <many-to-many class="com.wjt276.hibernate.Role" column="roleid"/>
        </set>
    </class>


(六) annotation註解方式

注意:因爲是多對多單向(當然用戶擁有多個角色,一個角色也可屬性多個用戶,但這裏角色看不到用戶),所以角色只需要正常註解就可以了,

現在要使用@ManyToMany來註解多對多的關係,並使用@JoinTabel來註解第三方表的名稱,再使用joinColumns屬性來指定當前對象在第三方表中的字段名,並且這個字段會指向當前類相對應的表,最後再用inverseJoinColumns來指定當前類持有引用的實體類在第三方表中的字段名,並且指向被引用對象相對應的表,如下:

@Entity
public class User {
  private int id;
  private String name;
  private Set<User> roles = new HashSet<User>();// Role對象的集合  @Id
  @GeneratedValue
  public int getId() {return id;}
 
  @ManyToMany
  @JoinTable(name="u_r",//使用@JoinTable標籤的name屬性註解第三方表名稱
          joinColumns={@JoinColumn(name="userId")},
//使用joinColumns屬性來註解當前實體類在第三方表中的字段名稱並指向該對象
          inverseJoinColumns={@JoinColumn(name="roleId")}
//使用inverseJoinColumns屬性來註解當前實體類持有引用對象在第三方表中的字段名稱並指向被引用對象表
  )
  public Set<User> getRoles() {return roles;}
}


(七) 生成SQL語句

create table t_role (id integer not null auto_increment, namevarchar(255), primary key (id))
create table t_user (id integer not null auto_increment, namevarchar(255), primary key (id))
create table t_user_role (userid integer not null, roleid integer notnull, primary key (userid, roleid))
alter table t_user_role add index FK331DEE5F1FB4B2D4 (roleid), add constraint FK331DEE5F1FB4B2D4 foreign key (roleid) referencest_role (id)
alter table t_user_role add index FK331DEE5F250A083E(userid), add constraint FK331DEE5F250A083E foreign key (userid) referencest_user (id)


注:根據DDL語句可以看出第三方表的主鍵是一個複合主鍵(primary key (userid, roleid)),也就是說記錄不可以有相同的數據。

(八) 數據庫表及結構:


(九) 多對多關聯映射 單向數據存儲:

session = HibernateUtils.getSession();
          tx =session.beginTransaction();
 
          Role r1 = new Role();
          r1.setName("數據錄入人員");
          session.save(r1);
         
          Role r2 = new Role();
          r2.setName("商務主管");
          session.save(r2);
         
          Role r3 = new Role();
          r3.setName("大區經理");
          session.save(r3);
         
          User u1 = new User();
          u1.setName("10");
          Set<Role>u1Roles = new HashSet<Role>();
          u1Roles.add(r1);
          u1Roles.add(r2);
          u1.setRoles(u1Roles);
         
          User u2 = new User();
          u2.setName("祖兒");
          Set<Role>u2Roles = new HashSet<Role>();
          u2Roles.add(r2);
          u2Roles.add(r3);
          u2.setRoles(u2Roles);
         
          User u3 = new User();
          u3.setName("成龍");
          Set<Role>u3Roles = new HashSet<Role>();
          u3Roles.add(r1);
          u3Roles.add(r2);
          u3Roles.add(r3);
          u3.setRoles(u3Roles);
         
          session.save(u1);
          session.save(u2);
          session.save(u3);
         
          tx.commit();

發出SQL語句:

Hibernate: insert into t_role (name) values (?)

Hibernate: insert into t_role (name) values (?)

Hibernate: insert into t_role (name) values (?)

Hibernate: insert into t_user (name) values (?)

Hibernate: insert into t_user (name) values (?)

Hibernate: insert into t_user (name) values (?)

Hibernate: insert into t_user_role (userid, roleid)values (?, ?)

Hibernate: insert into t_user_role (userid, roleid)values (?, ?)

Hibernate: insert into t_user_role (userid, roleid)values (?, ?)

Hibernate: insert into t_user_role (userid, roleid)values (?, ?)

Hibernate: insert into t_user_role (userid, roleid)values (?, ?)

Hibernate: insert into t_user_role (userid, roleid)values (?, ?)

Hibernate: insert into t_user_role (userid, roleid)values (?, ?)

注:前三條SQL語句,添加Role記錄,第三條到第六條添加User,最後7條SQL語句是在向第三方表(t_user_role)中添加多對多關係(User與Role關係)

 

(十) 多對多關聯映射 單向數據加載:

        

 session =HibernateUtils.getSession();
         tx =session.beginTransaction();
        
         User user =(User)session.load(User.class, 1);
         System.out.println("user.name=" + user.getName());
         for(Iterator<Role> iter = user.getRoles().iterator();iter.hasNext();){
             Rolerole = (Role) iter.next();
             System.out.println(role.getName());
         }
         tx.commit();

生成SQL語句:

Hibernate: select user0_.id as id0_0_, user0_.name as name0_0_ from t_useruser0_ where user0_.id=?

user.name=10

Hibernate: select roles0_.userid as userid1_, roles0_.roleid as roleid1_,role1_.id as id2_0_, role1_.name as name2_0_ from t_user_role roles0_ leftouter join t_role role1_ on roles0_.roleid=role1_.id where roles0_.userid=?

商務主管

數據錄入人員

七、       多對多 - 雙向

    多對多關聯映射 雙向 兩方都持有對象引用,修改對象模型,但數據的存儲沒有變化。

(一)     xml方式:映射

再修改映射文件:

  

  <class name="com.wjt276.hibernate.Role" table="t_role">
        <id name="id">
            <generator class="native"/>
        </id>
        <property name="name" column="name"/>
        <!—order-by 屬性是第三方表哪個字段進行排序-->
        <set name="users" table="t_user_role"order-by="userid">
            <key column="roleid"/>
            <many-to-many class="com.wjt276.hibernate.User" column="userid"/>
        </set>  
</class>

注:數據的存儲與單向一樣。但一般維護這個多對多關係,只需要使用一方,而使另一方維護關係失效。

總結:

<!— order-by 屬性是第三方表哪個字段進行排序-->
      <set name="users" table="t_user_role"order-by="userid">
          <key column="roleid"/>
          <many-to-many class="com.wjt276.hibernate.User" column="userid"/>
      </set>

Ø        table屬性值必須和單向關聯中的table屬性值一致

Ø        <key>中column屬性值要與單向關聯中的<many-to-many>標籤中的column屬性值一致

Ø        在<many-to-many>中的column屬性值要與單向關聯中<key>標籤的column屬性值一致。

 

(二) annotation註解方式

多對多關聯映射 雙向 兩方都持有對象引用,修改對象模型,但數據的存儲沒有變化

只需要修改註解映射就可以了。

User實體類註解沒有變化和單向一樣:

@Entity
public class User {
  private int id;
  private Set<User> roles = new HashSet<User>();// Role對象的集合  @Id
  @GeneratedValue
  public int getId() {return id;}
  @ManyToMany
  @JoinTable(name="u_r",//使用@JoinTable標籤的name屬性註解第三方表名稱
          joinColumns={@JoinColumn(name="userId")},//使用joinColumns屬性來註解當前實體類在第三方表中的字段名稱並指向該對象
          inverseJoinColumns={@JoinColumn(name="roleId")}
//使用inverseJoinColumns屬性來註解當前實體類持有引用對象在第三方表中的字段名稱並指向被引用對象表
  )
  public Set<User> getRoles() {return roles;}
 }


Role實體類註解也非常的簡單:使用@ManyToMany註解,並使用mappedBy屬性指定引用對象持有自己的的屬性名

@Entity
public class Role {
  private int id;
  private String name;
  private Set<User> users = newHashSet<User>();
  @Id
  @GeneratedValue
  public int getId() {return id;  }
  @ManyToMany(mappedBy="roles")
  public Set<User>getUsers() {return users;}
  public voidsetUsers(Set<User> users) {this.users = users;  }
}


多對多關聯映射 雙向 數據加載

      

     session =HibernateUtils.getSession();
           tx =session.beginTransaction();
          
           Role role= (Role)session.load(Role.class, 1);
           System.out.println("role.name=" + role.getName());
           for(Iterator<User> iter = role.getUsers().iterator();iter.hasNext();){
               Useruser = iter.next();
               System.out.println("user.name=" + user.getName());
           }
           tx.commit();

生成SQL語句:

Hibernate: select role0_.id as id2_0_, role0_.name as name2_0_ from t_rolerole0_ where role0_.id=?

role.name=數據錄入人員

Hibernate: select users0_.roleid as roleid1_, users0_.userid as userid1_,user1_.id as id0_0_, user1_.name as name0_0_ from t_user_role users0_ leftouter join t_user user1_ on users0_.userid=user1_.id where users0_.roleid=?order by users0_.userid

user.name=10

user.name=成龍

八、       關聯關係中的CRUD_Cascade_Fetch

1、  設定cascade可以設定在持久化時對於關聯對象的操作(CUD,R歸Fetch管)

2、  cascade僅僅是幫助我們省了編程的麻煩而已,不要把它的作用看的太大

a)        cascade的屬性指明做什麼操作的時候關係對象是綁在一起的

b)        refresh=A裏面需要讀B改過之後的數據

3、  鐵律:雙向關係在程序中要設定雙向關聯

4、  鐵律:雙向一定需要設置mappedBy

5、  fetch

a)        鐵律:雙向不要兩邊設置Eager(會有多餘的查詢語句發出)

b)        對多方設置fetch的時候要謹慎,結合具體應用,一般用Lazy不用eager,特殊情況(多方數量不多的可以考慮,提高效率的時候可以考慮)

6、  O/RMapping編程模型

a)        映射模型

                                    i.  jpa annotation

                                   ii.  hibernate annotation extension

b)        編程接口

                                    i.  jpa

                                   ii.  hibernate

c)         數據查詢語言

                                    i.  HQL

                                   ii.  EJBQL(JPQL)

7、  要想刪除或者更新,先做load,除了精確知道ID之外

8、  如果想消除關聯關係,先設定關係爲null,再刪除對應記錄,如果不刪記錄,該記錄就變成垃圾數據

九、       集合映射

1、 Set

2、 List

a)        @OrderBy

注意:List與Set註解是一樣的,就是把Set更改爲List就可以了

  private List<User>users = new ArrayList<User>();
  @OneToMany(mappedBy="group",
          cascade={CascadeType.ALL}
          )
  @OrderBy("name ASC")//使用@OrderBy註解List中使用哪個字段進行排序,可以組合排序,中間使用逗號分開
  public List<User>getUsers() {  return users;}
  public voidsetUsers(List<User> users) {this.users = users;}
 


3、 Map

a)        @Mapkey

註解:關聯模型中並沒有變化,只是註解發生了變化,而這個變化是給hibernate看的

Map在映射時,因爲Key是唯一的,所以一般可以使用主鍵表示,然後在映射時使用@MapKey來註解使用哪個字段爲key,如下:

private Map<Integer,User> users = new HashMap<Integer, User>();
@OneToMany(mappedBy="group", cascade=CascadeType.ALL)
@MapKey(name="id")//註解使用哪個字段爲key
public Map<Integer,User> getUsers() {  return users;}
public voidsetUsers(Map<Integer, User> users) {this.users = users;}
                   數據加載:
     Session s = sessionFactory.getCurrentSession();
     s.beginTransaction();
     Group g =(Group)s.load(Group.class, 1);
     for(Map.Entry<Integer,User> entry : g.getUsers().entrySet()) {
         System.out.println(entry.getValue().getName());
     }
     s.getTransaction().commit();


 

十、        繼承關聯映射

繼承映射:就是把類的繼承關係映射到數據庫裏(首先正確的存儲,再正確的加載數據)

(一) 繼承關聯映射的分類:

Ø        單表繼承:每棵類繼承樹使用一個表(table per class hierarchy)

Ø        具體表繼承:每個子類一個表(table per subclass)

Ø        類表繼承:每個具體類一個表(table per concrete class)(有一些限制)

Ø        實例環境:動物Animal有三個基本屬性,然後有一個Pig繼承了它並擴展了一個屬性,還有一個Brid也繼承了並且擴展了一個屬性

(二) 對象模型:


(三) 單表繼承SINGLE_TABLE

每棵類繼承樹使用一個表

把所有的屬性都要存儲表中,目前至少需要5個字段,另外需要加入一個標識字段(表示哪個具體的子類)

t_animal

Id

Name

Sex

Weight

Height

Type

1

豬豬

true

100

 

P

2

鳥鳥

false

 

50

B

其中:

           ①、id:表主鍵

           ②、name:動物的姓名,所有的動物都有

           ③、sex:動物的性別,所有的動物都有

           ④、weight:豬(Pig)的重量,只有豬纔有,所以鳥鳥就沒有重量數據

           ⑤、height:鳥(height)的調試,只有鳥纔有,所以豬豬就沒有高度數據

           ⑥、type:表示動物的類型;P表示豬;B表示鳥

1、 實體類

Animal實體類:

public class Animal {
  private int id;
  private String name;   
  private boolean sex;
  public int getId() {return id;  }
  public void setId(int id) { this.id = id;}
  public String getName() {return name;}
  public void setName(Stringname) {this.name = name;}
  public boolean isSex() {return sex;}
  public void setSex(boolean sex) {this.sex = sex;}  
}


Pig實體類:

public class Pig extends Animal {
  private int weight;
  public int getWeight() {return weight;}
  public void setWeight(int weight) {this.weight = weight;}
}


Bird實體類:

public class Bird extends Animal {
  private int height;
  public int getHeight() {return height;}
  public void setHeight(int height) {this.height = height;}  
}


2、 xml方式:映射

  

<class name="Animal" table="t_animal"lazy="false">
      <id name="id">
          <generator class="native"/>
      </id>
      <discriminator column="type" type="string"/>
      <property name="name"/>
      <property name="sex"/>
      <subclass name="Pig" discriminator-value="P">
          <property name="weight"/>
      </subclass>
      <subclass name="Bird" discriminator-value="B">
          <property name="height"/>
      </subclass>
  </class>

 

1、理解如何映射

      因爲類繼承樹肯定是對應多個類,要把多個類的信息存放在一張表中,必須有某種機制來區分哪些記錄是屬於哪個類的。

  這種機制就是,在表中添加一個字段,用這個字段的值來進行區分。用hibernate實現這種策略的時候,有如下步驟:

  父類用普通的<class>標籤定義

  在父類中定義一個discriminator,即指定這個區分的字段的名稱和類型

  如:<discriminator column=”XXX” type=”string”/>

  子類使用<subclass>標籤定義,在定義subclass的時候,需要注意如下幾點:

  Subclass標籤的name屬性是子類的全路徑名

  在Subclass標籤中,用discriminator-value屬性來標明本子類的discriminator字段(用來區分不同類的字段)

  的值Subclass標籤,既可以被class標籤所包含(這種包含關係正是表明了類之間的繼承關係),也可以與class標

  籤平行。 當subclass標籤的定義與class標籤平行的時候,需要在subclass標籤中,添加extends屬性,裏面的值

  是父類的全路徑名稱。子類的其它屬性,像普通類一樣,定義在subclass標籤的內部。

 

2、理解如何存儲

  存儲的時候hibernate會自動將鑑別字段值插入到數據庫中,在加載數據的時候,hibernate能根據這個鑑別值

  正確的加載對象

 

多態查詢:在hibernate加載數據的時候能鑑別出正真的類型(instanceOf)

 

get支持多態查詢

load只有在lazy=false,才支持多態查詢

hql支持多態查詢

3、 annotation註解

父類中註解如下:

 使用@Inheritance註解爲繼承映射,再使用strategy屬性來指定繼承映射的方式

 strategy有三個值:InheritanceType.SINGLE_TABLE           單表繼承

                  InheritanceType.TABLE_PER_CLASS       類表繼承

                  InheritanceType.JOINED            具體表繼承

 再使用@DiscriminatorColumn注意標識字段的字段名,及字段類型

 在類中使用@DiscriminatorValue來註解標識字段的值

@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(
name="discriminator",
discriminatorType=DiscriminatorType.STRING)
@DiscriminatorValue("person")
public class Person {private int id;private String name;
 
  @Id
  @GeneratedValue
public int getId() {return id;}


繼承類中註解如下:

    只需要使用@DiscriminatorValue來註解標識字段的值

 

4、 導出後生成SQL語句:

create table t_animal (id integer not null auto_increment, typevarchar(255) not null, name varchar(255), sex bit, weight integer, heightinteger, primary key (id))


5、 數據庫中表結構:



6、 單表繼承數據存儲:

session = HibernateUtils.getSession();
            tx =session.beginTransaction();
   
            Pig pig = new Pig();
            pig.setName("豬豬");
            pig.setSex(true);
            pig.setWeight(100);
            session.save(pig);
           
            Bird bird = new Bird();
            bird.setName("鳥鳥");
            bird.setSex(false);
            bird.setHeight(50);
            session.save(bird);
           
            tx.commit();

存儲執行輸出SQL語句:

Hibernate: insert into t_animal (name, sex, weight, type) values (?, ?, ?,'P')//自動確定無height字段,並且設置標識符爲P

Hibernate: insert into t_animal (name, sex, height, type) values (?, ?, ?,'B') //自動確定無weight字段,並且設置標識符爲B

解釋:hibernate會根據單表繼承映射文件的配置內容,自動在插入數據時哪個子類需要插入哪些字段,並且自動插入標識符字符值(在映射文件中配置了)

 

7、 單表繼承映射數據加載(指定加載子類):

    

session =HibernateUtils.getSession();
    tx =session.beginTransaction();
           
    Pig pig =(Pig)session.load(Pig.class, 1);
    System.out.println("pig.name=" + pig.getName());
    System.out.println("pig.weight=" + pig.getWeight());
           
    Bird bird =(Bird)session.load(Bird.class, 2);
    System.out.println("bird.name" + bird.getName());
    System.out.println("bird.height=" + bird.getHeight());
           
    tx.commit();

8、 加載執行輸出SQL語句:

Hibernate: select pig0_.id as id0_0_, pig0_.name as name0_0_, pig0_.sex assex0_0_, pig0_.weight as weight0_0_ from t_animal pig0_ where pig0_.id=? and pig0_.type='P'

//hibernate會根據映射文件自動確定哪些字段(雖然表中有height,但hibernate並不會加載)屬於這個子類,並且確定標識符爲B(已經在配置文件中設置了)

pig.name=豬豬

pig.weight=100

Hibernate: select bird0_.id as id0_0_, bird0_.name as name0_0_, bird0_.sexas sex0_0_, bird0_.height as height0_0_ from t_animal bird0_ where bird0_.id=?and bird0_.type='B'

//hibernate會根據映射文件自動確定哪些字段雖然表中有weight,但hibernate並不會加載屬於這個子類,並且確定標識符爲B(已經在配置文件中設置了)

bird.name=鳥鳥

bird.height=50

9、 單表繼承映射數據加載(指定加載父類):

session = HibernateUtils.getSession();
            tx =session.beginTransaction();
           
            //不會發出SQL,返回一個代理類
            Animal animal =(Animal)session.load(Animal.class, 1);
            //發出SQL語句,並且加載所有字段的數據(因爲使用父類加載對象數據)
            System.out.println("animal.name=" + animal.getName());
            System.out.println("animal.sex=" + animal.isSex());
           
            tx.commit();

加載執行生成SQL語句:

Hibernate: select animal0_.id as id0_0_, animal0_.name as name0_0_,animal0_.sex as sex0_0_, animal0_.weight asweight0_0_, animal0_.height as height0_0_, animal0_.type as type0_0_ from t_animal animal0_ where animal0_.id=?

//注:因爲使用父類加載數據,所以hibernate會將所有字段(height、weight、type)的數據全部加載,並且條件中沒有識別字段type(也就不區分什麼子類,把所有子類全部加載上來。)

10、 單表繼承映射數據加載(指定加載父類,看能否鑑別真實對象):

           

 session = HibernateUtils.getSession();
            tx =session.beginTransaction();
           
            //不會發出SQL語句(load默認支持延遲加載(lazy)),返回一個animal的代理對象(此代理類是繼承Animal生成的,也就是說是Animal一個子類)
            Animal animal =(Animal)session.load(Animal.class, 1);
           
            //因爲在上面返回的是一個代理類(父類的一個子類),所以animal不是Pig
            //通過instanceof是反應不出正直的對象類型的,因此load在默認情況下是不支持多態查詢的。
            if (animal instanceof Pig) {
                System.out.println("是豬");
            } else {
                System.out.println("不是豬");//這就是結果
            }
            System.out.println("animal.name=" + animal.getName());
            System.out.println("animal.sex=" + animal.isSex());
           
            tx.commit();

11、 多態查詢:

在hibernate加載數據的時候能鑑別出正直的類型(通過instanceof)

get支持多態查詢;load只有在lazy=false,才支持多態查詢;HQL支持多態查詢

12、 採用load,通過Animal查詢,將<class>標籤上的lazy設置爲false

session = HibernateUtils.getSession();
            tx = session.beginTransaction();
           
            //會發出SQL語句,因爲設置lazy爲false,不支持延遲加載
            Animal animal =(Animal)session.load(Animal.class, 1);
            //可以正確的判斷出Pig的類型,因爲lazy=false,返回具體的Pid類型
            //此時load支持多態查詢
            if (animal instanceof Pig) {
                System.out.println("是豬");//結果
            } else {
                System.out.println("不是豬");
            }
            System.out.println("animal.name=" + animal.getName());
            System.out.println("animal.sex=" + animal.isSex());
           
            tx.commit();

13、 採用get,通過Animal查詢,可以判斷出正直的類型

          

  session = HibernateUtils.getSession();
            tx = session.beginTransaction();
           
            //會發出SQL語句,因爲get不支持延遲加載,返回的是正直的類型,
            Animal animal =(Animal)session.load(Animal.class, 1);
 
            //可以判斷出正直的類型
            //get是支持多態查詢
            if (animal instanceof Pig) {
                System.out.println("是豬");//結果
            } else {
                System.out.println("不是豬");
            }
            System.out.println("animal.name=" + animal.getName());
            System.out.println("animal.sex=" + animal.isSex());
           
            tx.commit();

14、 採用HQL查詢,HQL是否支持多態查詢

List animalList = session.createQuery("from Animal").list();
            for (Iteratoriter = animalList.iterator(); iter.hasNext();) {
                Animal a = (Animal)iter.next();
                //能夠正確鑑別出正直的類型,HQL是支持多態查詢的。
                if (a instanceof Pig) {
                    System.out.println("是Pig");
                } else if (a instanceof Bird) {
                    System.out.println("是Bird");
                }
            }

 

15、 通過HQL查詢表中所有的實體對象

* HQL語句:session.createQuery("fromjava.lang.Object").list();

    * 因爲所有對象都是繼承Object類

List list = session.createQuery("from java.lang.Object").list();
            for (Iteratoriter = list.iterator(); iter.hasNext();){
                Object o =iter.next();
                if (o instanceof Pig) {
                    System.out.println("是Pig");
                } else {
                    System.out.println("是Bird");
                }
            }


 

 

(四) 具體表繼承JOINED

每個類映射成一個表(table per subclass)

對象模型不用變化,存儲模型需要變化


1、 關係模型:

每個類映射成一個表(table per subclass)



注:因爲需要每個類都映射成一張表,所以Animal也映射成一張表(t_animal),表中字段爲實體類屬性

而pig子類也需要映射成一張表(t_pid),但爲了與父類聯繫需要加入一個外鍵(pidid)指向父類映射成的表(t_animal),字段爲子類的擴展屬性。Bird子類同樣也映射成一張表(t_bird),也加入一個外鍵(birdid)指向父類映射成的表(t_animal),字段爲子類的擴展屬性。

2、 xml方式(每個類映射成一個表):

   

 <class name="com.wjt276.hibernate.Animal" table="t_animal">
        <id name="id"column="id"><!-- 映射主鍵 -->
            <generator class="native"/>
        </id>
        <property name="name"/><!-- 映射普通屬性 -->
        <property name="sex"/>
        <!--<joined-subclass>標籤:繼承映射 每個類映射成一個表 -->
        <joined-subclass name="com.wjt276.hibernate.Pig" table="t_pig">
<!-- <key>標籤:會在相應的表(當前映射的表)裏,加入一個外鍵 , 參照指向當前類的父類(當前Class標籤對象的表(t_animal))-->
            <key column="pigid"/>
            <property name="weight"/>
        </joined-subclass>
        <joined-subclass name="com.wjt276.hibernate.Bird" table="t_bird">
            <key column="birdid"/>
            <property name="height"/>
        </joined-subclass>
    </class>

1、理解如何映射

        這種策略是使用joined-subclass標籤來定義子類的。父類、子類,每個類都對應一張數據庫表。

    在父類對應的數據庫表中,實際上會存儲所有的記錄,包括父類和子類的記錄;在子類對應的數據庫表中,

    這個表只定義了子類中所特有的屬性映射的字段。子類與父類,通過相同的主鍵值來關聯。實現這種策略的時候,

    有如下步驟:

    父類用普通的<class>標籤定義即可

    父類不再需要定義discriminator字段

    子類用<joined-subclass>標籤定義,在定義joined-subclass的時候,需要注意如下幾點:

    Joined-subclass標籤的name屬性是子類的全路徑名

    Joined-subclass標籤需要包含一個key標籤,這個標籤指定了子類和父類之間是通過哪個字段來關聯的。

    如:<key column=”PARENT_KEY_ID”/>,這裏的column,實際上就是父類的主鍵對應的映射字段名稱。

    Joined-subclass標籤,既可以被class標籤所包含(這種包含關係正是表明了類之間的繼承關係),

    也可以與class標籤平行。 當Joined-subclass標籤的定義與class標籤平行的時候,需要在Joined-subclass

    標籤中,添加extends屬性,裏面的值是父類的全路徑名稱。子類的其它屬性,像普通類一樣,定義在joined-subclass標籤的內部。

3、 annotation註解

因爲,子類生成的表需要引用父類生成的表,所以只需要在父類設置具體表繼承映射就可以了,其它子類只需要使用@Entity註解就可以了

@Entity
@Inheritance(strategy=InheritanceType.JOINED)
 
public class Person {
    private int id;
    private String name;
    @Id
    @GeneratedValue
    public int getId() {return id;}
}


4、 導出輸出SQL語句:

create table t_animal (id integer notnull auto_increment, name varchar(255), sex bit, primary key (id))
create table t_bird (birdid integernot null, height integer, primary key (birdid))
create table t_pig (pigid integer notnull, weight integer, primary key (pigid))
altertable t_bird add index FKCB5B05A4A554009D(birdid), add constraint FKCB5B05A4A554009D foreign key (birdid) referencest_animal (id)
alter table t_pig add index FK68F8743FE77AC32 (pigid), add constraint FK68F8743FE77AC32 foreign key (pigid) references t_animal (id)


//共生成三個表,並在子類表中各有一個外鍵參照指向父類表

 

數據的存儲,不需要其它的任務變化,直接使用單表繼承存儲就可以了,加載也是一樣。

具體表繼承效率沒有單表繼承高,但是單表繼承會出現多餘的庸於字段,具體表層次分明

 

(五) 類表繼承TABLE_PER_CLASS

每個具體類映射成一個表(table per concreteclass)(有一些限制)

 

對象模型不用變化,存儲模型需要變化


1、 關係模型:

每個具體類(Pig、Brid)映射成一個表(table per concrete class)(有一些限制)

t_pig

Id

Name

Sex

Weight

1

豬豬

True

100

t_bird

Id

Name

Sex

Height

2

鳥鳥

False

50

2、 xml方式:映射文件:

   

 <class name="com.wjt276.hibernate.Animal" table="t_animal">
        <id name="id"column="id"><!-- 映射主鍵 -->
            <generator class="assigned"/><!-- 每個具體類映射一個表主鍵生成策略不可使用native -->       </id>   
        <property name="name"/><!-- 映射普通屬性 -->
        <property name="sex"/>
        <!--使用<union-subclass>標籤來映射"每個具體類映射成一張表"的映射關係
            ,實現上上面的表t_animal雖然映射到數據庫中,但它沒有任何作用。      -->
        <union-subclass name="com.wjt276.hibernate.Pig" table="t_pig">
            <property name="weight"/>
        </union-subclass>
        <union-subclass name="com.wjt276.hibernate.Bird" table="t_bird">
            <property name="height"/>
        </union-subclass>
    </class>

理解如何映射

    這種策略是使用union-subclass標籤來定義子類的。每個子類對應一張表,而且這個表的信息是完備的,

    即包含了所有從父類繼承下來的屬性映射的字段(這就是它跟joined-subclass的不同之處,

    joined-subclass定義的子類的表,只包含子類特有屬性映射的字段)。實現這種策略的時候,有如下步驟:

    父類用普通<class>標籤定義即可

    子類用<union-subclass>標籤定義,在定義union-subclass的時候,需要注意如下幾點:

    Union-subclass標籤不再需要包含key標籤(與joined-subclass不同)

    Union-subclass標籤,既可以被class標籤所包含(這種包含關係正是表明了類之間的繼承關係),

    也可以與class標籤平行。 當Union-subclass標籤的定義與class標籤平行的時候,需要在Union-subclass

    標籤中,添加extends屬性,裏面的值是父類的全路徑名稱。子類的其它屬性,像普通類一樣,

    定義在Union-subclass標籤的內部。這個時候,雖然在union-subclass裏面定義的只有子類的屬性,

    但是因爲它繼承了父類,所以,不需要定義其它的屬性,在映射到數據庫表的時候,依然包含了父類的所

    有屬性的映射字段。

    注意:在保存對象的時候id是不能重複的(不能使用自增生成主鍵)

3、 annotation註解

只需要對父類進行註解就可以了,

因爲子類表的ID是不可以重複,所以一般的主鍵生成策略已經不適應了,只有表主鍵生成策略。

首先使用@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)來註解繼承映射,並且使用具體表繼承方式,使用@TableGenerator來申明一個表主鍵生成策略

再在主鍵上@GeneratedValue(generator="t_gen", strategy=GenerationType.TABLE)來註解生成策略爲表生成策略,並且指定表生成策略的名稱

繼承類只需要使用@Entity進行註解就可以了

@Entity
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
@TableGenerator(
      name="t_gen",
      table="t_gen_table",
      pkColumnName="t_pk",
      valueColumnName="t_value",
      pkColumnValue="person_pk",
      initialValue=1,
      allocationSize=1
      )
public class Person {
private int id;
private String name;
 
  @Id
  @GeneratedValue(generator="t_gen", strategy=GenerationType.TABLE)
  public int getId() {return id;}
 


 

4、 導出輸出SQL語句:

create table t_animal (id integer not null, name varchar(255), sex bit, primary key (id))

create table t_bird (id integer not null, name varchar(255), sex bit, height integer, primary key(id))

create table t_pig (id integer not null, name varchar(255), sex bit, weight integer, primary key(id))

注:表t_animal、 t_bird、t_pig並不是自增的,是因爲bird、pig都是animal類,也就是說animal不可以有相同的ID號(Bird、Pig是類型,只是存儲的位置不同而以)

5、 數據庫表結構如下:



注:如果不想t_animal存在(因爲它沒有實際的作用),可以設置<class>標籤中的abstract="true"(抽象表),這樣在導出至數據庫時,就不會生成t_animal表了。

<class name="com.wjt276.hibernate.Animal" table="t_animal" abstract="true">

        <id name="id"column="id"><!-- 映射主鍵 -->

…………

(六) 三種繼承關聯映射的區別:

1、  第一種:它把所有的數據都存入一個表中,優點:效率好(操作的就是一個表);缺點:存在庸於字段,如果將庸於字段設置爲非空,則就無法存入數據;

2、  第二種:層次分明,缺點:效率不好(表間存在關聯表)

3、  第三種:主鍵字段不可以設置爲自增主鍵生成策略。

 

一般使用第一種



發佈了53 篇原創文章 · 獲贊 5 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章