【Hibernate步步爲營】--(一對多映射)之雙向關聯

一、一對多雙向關聯


        這裏繼續採用上篇文章的學生和班級作爲示例,班級和學生之間是一對多的關係,一個班級中擁有多名學生,和上篇文章不同的是這裏的關係是雙向的,也就是一的一端和多的一端同時維護關聯關係,所以它的對象圖如下:


      對應的關係模型圖沒有太大的變化,因爲它們之間的關係是雙向的,所以在關係模型中兩端同時維護關聯關係,映射到關係模型中如下圖所示:


      在一對多的單向關聯中映射文件只需要在一的一端進行特殊配置就可以,使用<one-to-many>配置,並在對象模型中使用set迭代器來設置外聯的對象模型,但是不同的是在雙向的關聯中需要在多的一端添加對應的另一端的外鍵關聯,這時候就必須在多的一端使用<many-to-one>的關聯關係來標明這種雙向性。


  1、映射


     這裏還使用Classes和Student來做示例,在Classes一端的內容和上文相同不會發生變換,但是多的一端Student的配置會發生變化,也就是在映射文件中需要添加<many-to-one>標籤。

     Student.hbm.xml映射文件配置需要添加外鍵列<many-to-one>標籤,並且該列的名稱要和Classes.hbm.xml的外鍵列的名稱一致,具體如下代碼:

  1. <?xml version="1.0"?>  
  2. <!DOCTYPE hibernate-mapping PUBLIC   
  3.     "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
  4.     "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
  5. <hibernate-mapping>  
  6.     <class name="com.src.hibernate.Student" table="t_student">  
  7.         <id name="id">  
  8.             <generator class="native"/>  
  9.         </id>  
  10.         <property name="name"/>  
  11.         <!-- 在多的一端Student中添加一行新的Classes列 ,並且列的名稱要和Classes.hbm.xml的列明相同-->  
  12.         <many-to-one name="classes" column="classesid"></many-to-one>  
  13.     </class>  
  14. </hibernate-mapping>  
<?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>
	<class name="com.src.hibernate.Student" table="t_student">
		<id name="id">
			<generator class="native"/>
		</id>
		<property name="name"/>
		<!-- 在多的一端Student中添加一行新的Classes列 ,並且列的名稱要和Classes.hbm.xml的列明相同-->
		<many-to-one name="classes" column="classesid"></many-to-one>
	</class>
</hibernate-mapping>

        Classes.hbm.xml映射文件的配置和上篇文章相同,需要注意的是在Classes.java文件中添加了set屬性映射對應了Student對象,所以在映射文件中需要添加set標籤來指示爲對象模型中使用了set迭代器,具體配置如下代碼:

  1. <?xml version="1.0"?>  
  2. <!DOCTYPE hibernate-mapping PUBLIC   
  3.     "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
  4.     "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
  5. <hibernate-mapping>  
  6.     <class name="com.src.hibernate.Classes" table="t_classes">  
  7.         <id name="id">  
  8.             <generator class="native"/>  
  9.         </id>  
  10.         <property name="name"/>  
  11.         <set name="students" inverse="true">  
  12.             <key column="classesid"></key>  
  13.             <one-to-many class="com.src.hibernate.Student"></one-to-many>  
  14.         </set>  
  15.     </class>  
  16. </hibernate-mapping>  
<?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>
	<class name="com.src.hibernate.Classes" table="t_classes">
		<id name="id">
			<generator class="native"/>
		</id>
		<property name="name"/>
		<set name="students" inverse="true">
			<key column="classesid"></key>
			<one-to-many class="com.src.hibernate.Student"></one-to-many>
		</set>
	</class>
</hibernate-mapping>

   2、類


      映射文件的配置是直接對應着類來的,所以有了映射文件就能夠寫出相應的類,相同的有了類就能夠知道對應的映射文件如何編寫,那來看看相應的類代碼如何編寫。

     Student.java類,需要在類中添加關聯的班級對象屬性,在加載Student時能獲得Classes的相關信息。

  1. package com.src.hibernate;  
  2.   
  3. public class Student {  
  4.       
  5.     //關聯的班級對象  
  6.     private Classes classes;  
  7.     public Classes getClasses() {  
  8.         return classes;  
  9.     }  
  10.     public void setClasses(Classes classes) {  
  11.         this.classes = classes;  
  12.     }  
  13.       
  14.     //學生id  
  15.     private int id;  
  16.     public int getId() {  
  17.         return id;  
  18.     }  
  19.     public void setId(int id) {  
  20.         this.id = id;  
  21.     }  
  22.       
  23.     //學生姓名  
  24.     private String name;  
  25.     public String getName() {  
  26.         return name;  
  27.     }  
  28.     public void setName(String name) {  
  29.         this.name = name;  
  30.     }  
  31.       
  32. }  
package com.src.hibernate;

public class Student {
	
	//關聯的班級對象
	private Classes classes;
	public Classes getClasses() {
		return classes;
	}
	public void setClasses(Classes classes) {
		this.classes = classes;
	}
	
	//學生id
	private int id;
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	
	//學生姓名
	private String name;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
}

        Classes.java文件具體代碼內容見上篇文章,這裏就不在詳述。

        有了對象模型接下來生成關係模型,生成的SQL語句如下:

  1. alter table t_student drop foreign key FK4B907570FC588BF4  
  2. drop table if exists t_classes  
  3. drop table if exists t_student  
  4. create table t_classes (id integer not null auto_increment, name varchar(255), primary key (id))  
  5. create table t_student (id integer not null auto_increment, name varchar(255), classesid integerprimary key (id))  
  6. alter table t_student add index FK4B907570FC588BF4 (classesid), add constraint FK4B907570FC588BF4 foreign key (classesid) references t_classes (id)  
alter table t_student drop foreign key FK4B907570FC588BF4
drop table if exists t_classes
drop table if exists t_student
create table t_classes (id integer not null auto_increment, name varchar(255), primary key (id))
create table t_student (id integer not null auto_increment, name varchar(255), classesid integer, primary key (id))
alter table t_student add index FK4B907570FC588BF4 (classesid), add constraint FK4B907570FC588BF4 foreign key (classesid) references t_classes (id)

 3、數據操作


      建立表結構後來編寫測試方法來驗證數據的操作,首先來看看數據的插入,向表結構中插入數據,寫入數據時會有兩種情況,一種是首先創建一個Classes對象,並將對象寫入到數據庫中,然後創建Student對象,在Classes對象中添加學生對象;另外一種是先創建學生對象,並將學生對象寫入數據庫中,然後創建Classes對象將學生對象加入到Classes對象中,這兩種類型的操作最後是不相同的,來對比下。


   3.1 先寫班級後寫學生

      先把班級寫入到數據庫中後,Classes對象進入了Transient狀態,並在數據庫中有了一行,這時再寫Student對象,Student對象會查找對應的Classes的主鍵將其寫入到表中,所以此時關係模型中的數據都是非空的,保存的代碼如下:

  1. public void testSave(){  
  2.     Session session=null;  
  3.     try{  
  4.         //創建session對象  
  5.         session=HibernateUtils.getSession();  
  6.         //開啓事務  
  7.         session.beginTransaction();  
  8.         //創建班級對象,將班級對象寫入到數據庫中  
  9.         Classes classes=new Classes();  
  10.         classes.setName("class");  
  11.         session.save(classes);  
  12.         //創建學生1對象,將學生對象寫入到數據庫中  
  13.         Student student1=new Student();  
  14.         student1.setName("zhangsan");  
  15.         student1.setClasses(classes);  
  16.         session.save(student1);  
  17.         //創建學生2對象,將學生對象寫入到數據庫中  
  18.         Student student2=new Student();  
  19.         student2.setName("lisi");  
  20.         student2.setClasses(classes);  
  21.         session.save(student2);  
  22.           
  23.         session.getTransaction().commit();  
  24.     }catch(Exception e){  
  25.         e.printStackTrace();  
  26.         session.getTransaction().rollback();  
  27.     }finally{  
  28.         HibernateUtils.closeSession(session);  
  29.     }  
  30. }  
public void testSave(){
	Session session=null;
	try{
		//創建session對象
		session=HibernateUtils.getSession();
		//開啓事務
		session.beginTransaction();
		//創建班級對象,將班級對象寫入到數據庫中
		Classes classes=new Classes();
		classes.setName("class");
		session.save(classes);
		//創建學生1對象,將學生對象寫入到數據庫中
		Student student1=new Student();
		student1.setName("zhangsan");
		student1.setClasses(classes);
		session.save(student1);
		//創建學生2對象,將學生對象寫入到數據庫中
		Student student2=new Student();
		student2.setName("lisi");
		student2.setClasses(classes);
		session.save(student2);
		
		session.getTransaction().commit();
	}catch(Exception e){
		e.printStackTrace();
		session.getTransaction().rollback();
	}finally{
		HibernateUtils.closeSession(session);
	}
}

     對應的寫入數據庫中的信息列表如下圖:



   3.2 先寫學生後寫班級

     先把學生寫入到數據庫中此時因爲學生表需要獲取對應的班級列的主鍵信息,但是因爲班級信息轉化到Transient狀態,所以在寫入學生信息時會有null值,代碼如下:

  1. public void testSave(){  
  2.     Session session=null;  
  3.     try{  
  4.         //創建session對象  
  5.         session=HibernateUtils.getSession();  
  6.         //開啓事務  
  7.         session.beginTransaction();  
  8.         //創建學生1對象,將學生對象寫入到數據庫中  
  9.         Student student1=new Student();  
  10.         student1.setName("zhangsan");  
  11.         session.save(student1);  
  12.         //創建學生2對象,將學生對象寫入到數據庫中  
  13.         Student student2=new Student();  
  14.         student2.setName("lisi");  
  15.         session.save(student2);  
  16.           
  17.         //創建班級對象  
  18.         Classes classes=new Classes();  
  19.         classes.setName("Classes");  
  20.         //設置學生集合  
  21.         Set students=new HashSet();  
  22.         students.add(student1);  
  23.         students.add(student2);  
  24.         //將學生集合寫入到Classes中  
  25.         classes.setStudents(students);  
  26.         //可以成功保存數據  
  27.         //但是會發出多餘的update語句來維持關係,因爲是一對多的原因  
  28.         session.save(classes);  
  29.         session.getTransaction().commit();  
  30.     }catch(Exception e){  
  31.         e.printStackTrace();  
  32.         session.getTransaction().rollback();  
  33.     }finally{  
  34.         HibernateUtils.closeSession(session);  
  35.     }  
  36. }  
public void testSave(){
	Session session=null;
	try{
		//創建session對象
		session=HibernateUtils.getSession();
		//開啓事務
		session.beginTransaction();
		//創建學生1對象,將學生對象寫入到數據庫中
		Student student1=new Student();
		student1.setName("zhangsan");
		session.save(student1);
		//創建學生2對象,將學生對象寫入到數據庫中
		Student student2=new Student();
		student2.setName("lisi");
		session.save(student2);
		
		//創建班級對象
		Classes classes=new Classes();
		classes.setName("Classes");
		//設置學生集合
		Set students=new HashSet();
		students.add(student1);
		students.add(student2);
		//將學生集合寫入到Classes中
		classes.setStudents(students);
		//可以成功保存數據
		//但是會發出多餘的update語句來維持關係,因爲是一對多的原因
		session.save(classes);
		session.getTransaction().commit();
	}catch(Exception e){
		e.printStackTrace();
		session.getTransaction().rollback();
	}finally{
		HibernateUtils.closeSession(session);
	}
}

     寫入後對應的數據庫視圖如下:


       對比兩種寫入操作,因爲兩個寫入的先後順序不同所以出現了不同的結果,但因爲是雙向的關聯關係所以在寫入時並不會發生異常。

    

   4、讀取操作


     相對於寫入數據而言,讀取數據就變得很簡單了,因爲是雙向的關聯所以數據的讀取也是雙向的,可以從任何一端讀取另一端的信息,如下代碼:
  1. public void testLoad1(){  
  2.     Session session=null;  
  3.     try{  
  4.         session=HibernateUtils.getSession();  
  5.         session.beginTransaction();  
  6.           
  7.         //通過班級讀取學生信息  
  8.         Classes classes=(Classes)session.load(Classes.class,1);  
  9.         System.out.println("classes.name="+classes.getName());  
  10.         Set students=classes.getStudents();  
  11.           
  12.         for(Iterator iter=students.iterator();iter.hasNext();){  
  13.             Student student=(Student)iter.next();  
  14.             System.out.println("student.name="+student.getName());  
  15.         }  
  16.           
  17.         //通過學生信息讀取班級信息  
  18.         Student stu=new Student();  
  19.         stu=(Student)session.load(Student.class1);  
  20.         System.out.println("通過學生加載班級信息Classes.id= "+stu.getClasses().getId());  
  21.         session.getTransaction().commit();  
  22.     }catch(Exception e){  
  23.         e.printStackTrace();  
  24.         session.getTransaction().rollback();  
  25.     }finally{  
  26.         HibernateUtils.closeSession(session);  
  27.     }  
  28. }  
public void testLoad1(){
	Session session=null;
	try{
		session=HibernateUtils.getSession();
		session.beginTransaction();
		
		//通過班級讀取學生信息
		Classes classes=(Classes)session.load(Classes.class,1);
		System.out.println("classes.name="+classes.getName());
		Set students=classes.getStudents();
		
		for(Iterator iter=students.iterator();iter.hasNext();){
			Student student=(Student)iter.next();
			System.out.println("student.name="+student.getName());
		}
		
		//通過學生信息讀取班級信息
		Student stu=new Student();
		stu=(Student)session.load(Student.class, 1);
		System.out.println("通過學生加載班級信息Classes.id= "+stu.getClasses().getId());
		session.getTransaction().commit();
	}catch(Exception e){
		e.printStackTrace();
		session.getTransaction().rollback();
	}finally{
		HibernateUtils.closeSession(session);
	}
}

       運行上面的測試語句,生成的對應的語句信息如下:
  1. Hibernate: select classes0_.id as id1_0_, classes0_.name as name1_0_ from t_classes classes0_ where classes0_.id=?  
  2. classes.name=class  
  3. Hibernate: select students0_.classesid as classesid1_, students0_.id as id1_, students0_.id as id0_0_, students0_.name as name0_0_, students0_.classesid as classesid0_0_ from t_student students0_ where students0_.classesid=?  
  4. student.name=lisi  
  5. student.name=zhangsan  
  6. 通過學生加載班級信息Classes.id1  
Hibernate: select classes0_.id as id1_0_, classes0_.name as name1_0_ from t_classes classes0_ where classes0_.id=?
classes.name=class
Hibernate: select students0_.classesid as classesid1_, students0_.id as id1_, students0_.id as id0_0_, students0_.name as name0_0_, students0_.classesid as classesid0_0_ from t_student students0_ where students0_.classesid=?
student.name=lisi
student.name=zhangsan
通過學生加載班級信息Classes.id= 1


結語


        雙向的一對多討論完成,如果在使用一對多關係時建議使用雙向的關聯關係,它可以優化關係的類型,而且也可以保證在寫入時不會出錯。總結兩篇文章,單向和雙向性其實是通過使用<one-to-many>和<many-to-one>來實現的,前者重在設置關聯關係,並不會生成新列,但是後者在生成關聯關係的同時會生成新列。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章