Hibernate一對多關聯映射—單向


一、簡述

一對多關聯映射(one-to-many)

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

比如Classes(班級)和Student(學生)之間是一對多的關係

public class Classes{

   private String id;

      private String name;

      private Set students;

}

 

public class Student{

       publicString id;

       publicString name;

}

2、我們以前學過學生對班級是多對一,返過來,班級對學生就是一對多。

3、我們多對一的關係是用戶和組。返過來看,從組這邊來看,就是一對多了。

下面學生的示例是班級和學生。和用戶和組是一樣的。

一個班級有多個學生,這是一對多的關係;返過來,多個學生屬於一個班級,這就是多對一了。

4、建立對象模型

 

5、這兩個對象模型之間是有關係的。我們現在講的是一對多。一的一端是班級。多的一端是學生。那麼怎麼樣能體現出這種關係呢?

我們在學習多對一時,是在多的一端加上一個字段。這個字段做爲外鍵關聯一的一端。多對一,就是我們在看到學生的時候,能夠知道這個學生是哪個班級的。或者是當我們看到用戶的時候,知道這個用戶是哪個組的。所以在用戶裏面持有組的引用。

6、那麼一對多,就是一個組裏面有多少個用戶。所以要維護這種關係,必須在組裏面持有用戶的集合。

班級和學生也是一樣的。一個班級有多少學生,所以在班級裏面要持有相應的學生的集合。

如下圖

 

我們用Set,通常用戶Set做映射。

箭頭表示兩者之間是有關係的。

7、上面的是對象模型,那麼這種模型要映射成什麼樣呢?

當我們定義多對一的關係時,在加載多的一端時,能夠把1的一端加載上來。因爲兩者之間是有關係的。

同理,一對多也是一樣的,它要維護這種關係。這種關係就是一對多。一的一端要指向多。

在維護這種關係時,在加載一的時候,就會把一的一端加載上來。

也就是說,在我在加載班級時,這個班級有多少個學生,它會把所有的學生自動查詢上來,放到Set集合裏面。這就是維護這個關係的目的。

8、我們知道,實體類要映射成表。所在下面畫兩個表。

 

依我們來看,是先有班級。再分配學生。

學生有了,班級有了。要保證知道一個班級有多少學生。

因爲students這個集合是在Classes上,要映射它的時候,那麼我們是要在t_classes表上加一個字段,然後將所有的學生用,表達式表達出來嗎?可是這樣做不符合數據庫的設計範式。

9、所以說,這種關係的維護應該是在t_student這一端。也就是說,在t_student表中加一個字段,這個字段就是classesid。

也就是說,一對多關聯映射,要把兩個表的關聯字段加到多的一端。

10、所以說,一對多與多對一的映射是一樣的。沒有區別。都在多的一端加一個外鍵,指向一的一端。

但是兩者也是有區別的。區別就是關係。如果維護的是多對一,則加載多的時候,能把1加上來。如果維護的是一對多,則加載班級時,能把WHSD1011對應的兩個學生加載上來。

 

我的理解:對於要相關聯的表來說,如果一個表想要看到對方的表內容,則就要在自己的實體類中持有對方的引用。

如果只有一方看到另一方,就是單向的。

如果要雙方都看到,就要在實體模型中彼此都持有對方的引用。

 

二、新建項目hibernate_one2many_1(拷貝hibernate_session)這個項目就O了。:我們這個實例還是單向的。只能在加載班級時,把所有的學生加載上來。但是當把學生拿上來的時候,看不到這個學生所在的班級。

三、建立對象模型

1、注意:一對多關聯映射,通常用Set這個集合,那麼爲什麼用Set呢?我們可以這樣理解:Set裏面的對象是不能重複的。當然也可以用其他的。不過一般情況下用Set。

一定要用Set這個接口,而不用HashSet。因爲Hibernate中有延遲加載。實體對象就實現了延遲加載。也就是採用代理的方式。集合也有延遲加載。hibernate中對JDK中的Set集合進行了擴展,也就是實現了這個接口,所以不能用HashSet。

所以要用Set接口。因爲hibernate對Set有相應的實現,對Set進行了擴展。

2、我們的Set裏面就是Student對象的集合。這樣就構成了一對多的關係。

2.1 Classes.java

 

package com.bjsxt.hibernate;

import java.util.Set;

public class Classes {

    private int id;

    private String name;

    private Set students;

    public Set getStudents(){

       returnstudents;

    }

    public voidsetStudents(Set students) {

      this.students = students;

    }

   

    public int getId() {

       returnid;

    }

    public void setId(intid) {

      this.id = id;

    }

    public String getName(){

       returnname;

    }

    public voidsetName(String name) {

      this.name = name;

    }

 

}

 

2.2Student.java

 

package com.bjsxt.hibernate;

public class Student {

    private int id;

    private String name;

    public int getId() {

       returnid;

    }

    public void setId(intid) {

      this.id = id;

    }

    public String getName(){

       return name;

    }

    public voidsetName(String name) {

      this.name = name;

    }

 

}

 

四、對象模型建立好之後,就開始寫映射文件,這是hibernate開發的正確思路。

1、在寫映射文件時,先從簡單的寫起。

寫Students.hmb.xml文件。

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC

   "-//Hibernate/Hibernate Mapping DTD 3.0//EN"

   "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>

    <classname="com.bjsxt.hibernate.Student" table="t_student">

       <idname="id">

          <!-- 主鍵的生成方式不能是uuid。因爲這種生成方式是生成32位16進制的字符串

             而我們的實體類中id的類型爲int.所以要修改主鍵的生成方式爲native.就是以數字形式自增 -->

          <generator class="native"></generator>

       </id>

      <property name="name"/>

    </class>

</hibernate-mapping>

 

2、再映射難一點的,Classes.hbm.xml文件如下 :

 

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC

   "-//Hibernate/Hibernate Mapping DTD 3.0//EN"

    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mappingpackage="com.bjsxt.hibernate">

    <classname="Classes"  table="t_classes">

       <idname="id">

        

          <generatorclass="native"></generator>

       </id>

      <property name="name"/>

       <!-- 上面爲簡單屬性

        下面要看一下集合要如何映射

        答:集合要用set標籤來映射

        set標籤的名字屬性的值就是Classes類中的集合類型數據的變量名 -->

       <setname="students">

         <!-- 那麼要如何映射set裏面的東西呢?

            我們知道ont2many的映射關係,與many2one的映射是一樣的,要在多的一端加字段的。

            但是到目前爲止,在t_student這張表裏面是沒有classid這個字段標識的。

            所以要把這個字段加過來,加過來之後,還要做爲外鍵指向t_classes這張表的主鍵

            我們用key標籤來實現 -->

            <!-- 在key標籤裏面要使用的屬性是列,就是給定在t_student表中的加上的列的名字。

            加了key這個標籤後,就會把classid這個字段加入到t_student這張表裏面了,

            它做爲外鍵指向t_class表的id字段。 -->

         <keycolumn="classid"></key>

         <!-- 接下來,採用one-to-many這個標籤,採用這個標籤,一方面是一對多,

            另一方面,要用class這個屬性來指定Classes.java類中的students這個集合裏面

            到底是什麼元素。我們這個實例裏面是Student對象集合。

            一定要指定集合中的元素是什麼類型的對象。-->

         <one-to-many class="Student"/>

         <!--ont-to-many標籤就代表了students集合裏面是Student對象。

         這樣指明之後,classes就能找到student這張表。

         因爲student在這裏都寫了。

         而且在Student.hbm.xml文件中,也已經映射Student對象映射到哪張表了。

         這樣就可以把classid加到表t_student裏面,而且做爲外鍵指向t_classes表的主鍵id. -->

      </set>

    </class>

</hibernate-mapping>

 

五、到hibernate.cfg.xml文件中,修改數據庫,並把我們的兩個實體類加到配置文件中

文件內容爲:

<!DOCTYPE hibernate-configuration PUBLIC

   "-//Hibernate/Hibernate Configuration DTD 3.0//EN"

   "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

 

<hibernate-configuration>

    <session-factory >

        <!-- 數據庫改成hibernate_session-->

      <propertyname="hibernate.connection.url">jdbc:mysql://localhost/hibernate_one2many_1</property>

       <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>

       <propertyname="hibernate.connection.username">root</property>

       <propertyname="hibernate.connection.password">root</property>

       <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>

       <property name="hibernate.show_sql">true</property>

      

       <mapping resource="com/bjsxt/hibernate/Classes.hbm.xml"/>

       <mapping resource="com/bjsxt/hibernate/Student.hbm.xml"/>

    </session-factory>

</hibernate-configuration>

 

六、打MySql,創建數據庫

root;

create database hibernate_one2many_1;

use hibernate_one2many_1;

show tables;

 

七、執行ExportDB.class類,將對象導出爲表

ExportDB.class 文件內容爲:

package com.bjsxt.hibernate;

 

import org.hibernate.cfg.Configuration;

importorg.hibernate.tool.hbm2ddl.SchemaExport;

 

//這個類寫完之後,以後就再也不用寫了。

//這個類就是把hibernate的配置文件,轉換成DDL。

public class ExportDB {

      publicstatic void main(String[] args){

         //讀取hibernate.cfg.xml文件

         Configuration cfg=new Configuration().configure(); //hibernate中的一個api,是Configuraton。引入時,將光標定位在Configuration上

                        //右擊,選擇"Source"命令的"Add Import"命令,找到hibernate的Configuration。

                        //它是用來讀取hibernate中的配置文件的。即hibernate.cfg.xml文件。(相當於Struts中的

                        //ActionServlet)

                        //這樣直接寫 Configuration cfg=newConfiguration();會有問題,因爲

                      //hibernate有兩個配置文件,一個是property類型的,一個是xml類型的,這樣

                //new完,就會默認的讀取property類型的配置文件,這樣就會有問題。所以必須加上它的一個

                   //方法configure()。

        //hibernate中有一個工具類,叫SchemaExport,這個工具類需要傳送configuration.

         //這個對象就把我們的類生成(導成)表出來。

         SchemaExport export=new SchemaExport(cfg);

         export.create(true, true);  //script是判斷生成不生成,它實際是生成ddl.

                                           //這兩個參數都設置成true就可以了。

                           //這個方法的具體含義可以直接看它的api。

               //或者關聯上它的源代碼,可以在create上,點擊F3,

                 //選擇"AttachResource",選擇“ExternalFile",

                //再找到F:\javaprogram\Hibernate相關資料\hibernate-3.2.0.ga,就關聯上了。

                

                      //export.create(..)是拿到對象,直接生成就可以了。

         

     

     

     }                

}

 

 

八、導出表後,在控制檯顯示:

drop table if exists t_classes

 

drop table if exists t_student

 

create table t_classes (id integer not nullauto_increment, name varchar(255), primary key (id))

create table t_student (id integer not nullauto_increment, name varchar(255), classid integer, primary key (id))

注意:在student表中加的字段classid是在寫Classes.hbm.xml映射文件時,key標籤起的作用哦!

 

 

alter table t_student add indexFK4B907570C229DCC9 (classid), add constraint FK4B907570C229DCC9 foreign key(classid) references t_classes (id)

這條語句是在t_student這張表中加了約束。也就是classid作爲外鍵參照了t_classes的id字段。

 

 

2、到MysQL中

輸入show tables;

desc t_classes;

+-------+--------------+------+-----+---------+----------------+

| Field |Type         | Null | Key | Default |Extra          |

+-------+--------------+------+-----+---------+----------------+

| id    |int(11)      | NO   | PRI |NULL    | auto_increment |

| name  | varchar(255) | YES |     | NULL   |               |

+-------+--------------+------+-----+---------+----------------+

 

輸入命令:

desc t_student;

顯示結果爲:我們發現在t_student這張表中加了一個字段classid。

+---------+--------------+------+-----+---------+----------------+

| Field   |Type         | Null | Key | Default |Extra          |

+---------+--------------+------+-----+---------+----------------+

| id      |int(11)      | NO   | PRI |NULL    | auto_increment |

| name    | varchar(255) |YES  |     | NULL   |               |

| classid |int(11)      | YES  | MUL |NULL   |               |

+---------+--------------+------+-----+---------+----------------+

 

九、所以我們項目上用hibernate進行持久層映射時,最重要的是要發現對象,然後建立對象之間的關係

 

十、readme.xml文件內容爲:

hibernate 一對多關聯映射。(單向日葵 Classed----->Student)

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

  

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

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

   也就是說,一對多與多對一映射策略是一樣的,只不過站的角度不同。

 

 

 

 

hibernate一對多關聯映射——單向(繼。空間不夠了)

十一、接着看存儲與加載

建立單元測試類One2ManyTest

package com.bjsxt.hibernate;

import java.util.HashSet;

import java.util.Set;

 

import org.hibernate.Session;

import junit.framework.TestCase;

public class One2ManyTest extends TestCase {

    }

十二、測試save(保存)

一對多的存儲方式應該與多對一不一樣。一對多存儲的應該是classes。因爲班級與學生的關係是在classes類裏面的students集合。所以應該是先有學生,把學生建立好之後,再放到Classes類的students這個集合裏面。

1、testSave()方法內容如下:

 

 

    public void testSave(){

       Session session=null;

       try{

          session=HibernateUtils.getSession();

          session.beginTransaction();

          

          Student student1=new Student();

          student1.setName("菜10");

          Student student2=new Student();

          student2.setName("容祖兒");

          

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

          students.add(student1);

          students.add(student2);

          

          Classes classes=new Classes();

          classes.setName("尚學堂");

          classes.setStudents(students);

          session.save(classes);

          

          session.getTransaction().commit();

       }catch(Exceptione){

          e.printStackTrace();

          session.getTransaction().rollback();

       }finally{

          HibernateUtils.closeSession(session);

       }

    }

   

當testSave()方法執行時,會出現這個org.hibernate.TransientObjectException: object references an unsaved transientinstance - save the transient instance before flushing:com.bjsxt.hibernate.Student 異常。

 

2、正確的保存testSave2()

 

 

    public void testSave2(){

       Session session=null;

       try{

          session=HibernateUtils.getSession();

          session.beginTransaction();

          

          //先建立學生對象集合

          Student student1=new Student();

          student1.setName("菜10");

          session.save(student1);

          Student student2=new Student();

          student2.setName("容祖兒");

          session.save(student2);

          

          //用泛型了

          //因爲班級裏的學生是一個集合,所以要構造一個集合出來

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

          //向集合裏面加數據

          students.add(student1);

          students.add(student2);

          

          //要知道哪個學生所在的班級,要new 班級出來

          Classes classes=new Classes();

          classes.setName("尚學堂");

          //這個班級裏面有哪些學生,要用set方法加上去。

          //這樣這個尚學堂班級裏面就有兩個學生了

          classes.setStudents(students);

          session.save(classes);

          

          session.getTransaction().commit();

       }catch(Exceptione){

          e.printStackTrace();

          session.getTransaction().rollback();

       }finally{

          HibernateUtils.closeSession(session);

       }

    }

2.1執行後出現SQL語句:

insert into t_student (name) values (?) 

只保存了學生的name,此時classesid字段值爲null.

insert into t_student (name) values (?)

insert into t_classes (name) values (?)

hibernate又連接發了兩條update,因爲只有兩個學生

update就是要把學生的classesid字段值更新上

根據學生id把classesid更新上。

也就是說,要由班級(一的一端)來維護這種關係:你菜10是我們班的,你容祖兒是我們班的。

在一的一端維護這種關係,會發現很多udpate。因爲班級不知道會有多少學生。

就是說,由班級(一的端)主動記住這種關係

就如記人的名字一樣,就如讓姚明來記我們十三億的人,這個關係要由姚來維護,

他要主動來記,把十三億人的名字全部記下來。這肯定比較困難。因爲由他一個人來更新,來記。

實際上,這種關係在多的一端來維護很容易,就如讓我們記住姚明的名字很容易一樣的。這是在一的端維護關係的一個缺點。另一個缺點是後面說的,如果在Student.hbm.xml映射文件中,將classesid字段設置爲非空的話,則保存時會出錯。

update t_student set classid=? where id=?

update t_student set classid=? where id=?

 

2.2執行結果數據庫顯示:

 

mysql> select * from t_classes;

+----+--------+

| id | name   |

+----+--------+

|  1 | 尚學堂 |

+----+--------+

1 row in set (0.00 sec)

 

mysql> select * from t_student;

+----+--------+--------

| id | name   |classesid

+----+--------+-------

|  1 | 菜10   |   1

+----+--------+-------

|  2 | 容祖兒  |   1

+----+--------+-------

1 row in set (0.00 sec)

 

2.3 我們現在分析一下數據庫的結構,如這個測試方法,我們是先保存學生對象集合的。可是這時,這個學生對象裏面只有名字與id,卻沒有classesid .可是表字段裏是有classesid字段的。是在映射的時候加進去的。

可是如我們的測試方法,還是可以正確執行,班級裏面可以加進去兩個學生。這是爲什麼呢?

原因是,我們的classesid字段是可以爲空的。

也就是說,我們先把學生保存進去,可是這時 classesid  字段是沒有值的。等到保存Classes時,再更新這個字段,將班級id值更新(update)上去。

如果我們在此Students.hbm.xml文件加上這樣一條,讓 classesid字段不能爲空,再執行這個方法時,在存儲時就會失敗了,因爲clssesid字段在保存時必須要值,不然就存不進去了。

<set name=”students”>

   <key column=”classesid”not-null=”true”/>

   <ont-to-manyclass=”com.bjsxt.hibernate.Student”/>

</set>

注:當修改完Students.hbm.xml文件時,要重新執行ExportDB.class文件,因爲這裏數據庫中的表結構發生改變了。所以要重新生成表。

 

十三、測試加載

 

public voidtestLoad1(){

       Session session=null;

       try{

          session=HibernateUtils.getSession();

          Classes classes=(Classes)session.load(Classes.class,1);

          Set students=classes.getStudents();

          for(Iterator iter=students.iterator();iter.hasNext();){

             Student student=(Student)iter.next();

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

          }

          session.getTransaction().commit();

          

       }catch(Exceptione){

          e.printStackTrace();

          session.getTransaction().rollback();

       }finally{

          HibernateUtils.closeSession(session);

       }

    }

   

 

   

   

    public void testLoad2(){

       Session session=null;

       try{

          session=HibernateUtils.getSession();

          session.beginTransaction();

          //注:load與get只能根據主鍵加載,根據別的字段是不可以的。

          //第一個參數是相應的類

          Classes classes=(Classes)session.load(Classes.class, 1);

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

          //拿出班級的學生,它是一個集合,然後一個一個輸出學生名字。

          Set<Student> students=classes.getStudents();

          for(Iterator<Student> iter=students.iterator();iter.hasNext();){

             Student student=iter.next();

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

          }

          session.getTransaction().rollback();

       }catch(Exceptione){

          e.printStackTrace();

       }finally{

          HibernateUtils.closeSession(session);

       }

    }

 

 

執行後SQL語句爲:

先根據id到班級裏面來查詢

select classes0_.id as id0_0_, classes0_.name as name0_0_from t_classes classes0_ where classes0_.id=?

班級查上來後,就可以把班級的學生查詢上來。因爲班級維護了一指向多的關係,所以它要找到這個關係字段,把班級的id拿來,然後它到t_student這張表中,找維護兩個表關係的字段,就是classesid=這個班級的學生,把它一個一個的拿上來,它會自動地把這些學生放到學生集合裏面。

select students0_.classesid as classesid1_, students0_.idas id1_, students0_.id as id1_0_, students0_.name as name1_0_ from t_studentstudents0_ where students0_.classesid=?

 

再回想一下一對多,查詢用戶時,也可以把組加上來。之所以可以這樣,是因爲我們配置了關係,如果沒有在映射文件裏面配置這種關係,則hibernate就不會給我們加上來了。

 

十四、readme.txt文件

hibernate 一對多關聯映射。(單向日葵Classed----->Student)

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

  

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

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

   也就是說,一對多與多對一映射策略是一樣的,只不過站的角度不同。

  

因爲是在一的一端維護的關係(爲什麼知道是在一的一端維護的關係呢?

因爲我們將關係的配置寫到一的一端就是Classes.hbm.xml文件裏面了。

缺點:在一端維護關係的缺點:

   *如果將t_student表裏的classesid字段設置爲非空,則無法保存

   *因爲在一的一端維護的,所以剛開始建立時,學生是不知道她是哪個班級的。

       所以一的一端要發出多餘的update語句。

       因爲不是在student這一端維護關係(就像我沒有記住你的電話號,所以找不到你),

       所以student不知道自己是哪個班的。所以需要發出多餘的update語句來更新關係。

      

基於以上的缺點,所以一對多關聯映射通常要做成雙向的。就可以克服這些缺點了。雙向的返過來就是多對一。

爲什麼要做成雙向的,最大的考慮,是將關係交給多的一端來維護,不讓一的一端來做了。

就像不能讓姚明記住十三億人的名字一樣,我們記住姚的就可以了。

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章