一、一對多單向關聯映射
一對多關系的對象模型在日常生活中也經常看到,就拿學生和班級來說,一個班級里有多個學生,所以班級和學生的關系是一對多的關系,映射到對象模型中,如下圖:
對象模型說明了這種一對多的關系是由一的一端來維護的,那么映射成關系模型就是一個班級字段下面會有多個學生,這樣就形成了一對多的關系,通過班級能夠查詢獲得學生信息,對應的關系模型如下圖:
1、基本配置
有了對象模型接下來就讓它們映射為對應的關系代碼,在進行關系映射時需要在一的一端添加<one-to-many>標簽,另外還需要在一的一端添加Set屬性,它支持延遲加載,然后在映射文件添加set標簽,并指明一對多的關系,這樣就能夠在一的一端查詢獲取多的一端。
Classes類及映射文件:
它是模型中最重要的一端,在該端需要添加對應的set屬性,并在配置文件中添加set標簽,在set標簽中配置相應的<one-to-many>對象,具體Classes.java對象代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
package com.src.hibernate; import java.util.Set; public class Classes { private int id; 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; } private String name; //Set支持延遲加載 private Set students; public Set getStudents() { return students; } public void setStudents(Set students) { this .students = students; } } |
Classes對象中使用了set屬性,但是只是說明了延遲加載的屬性,并沒有為屬性配置對應的對象,屬性的對象是要在映射文件中來配置的,需要添加set標簽,并在set標簽中添加<one-to-many>標簽,具體如下代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<? 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.hibernate.Classes" table = "t_classes" > < id name = "id" > < generator class = "native" /> </ id > < property name = "name" /> < set name = "students" > < key column = "classesid" ></ key > < one-to-many class = "com.hibernate.Student" ></ one-to-many > </ set > </ class > </ hibernate-mapping > |
對應的Student對象中的代碼和映射文件不需要什么特殊的配置,只需要按照通常的寫法編寫即可,具體的配置方法不再詳述,很簡單。配置好后需要生成對應的SQL語句,將對象模型轉化為關系模型時Hibernate生成相應的語句如下:
1
2
3
4
5
6
|
alter table t_student drop foreign key FK4B9075705E0AFEFE 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 FK4B9075705E0AFEFE (classesid), add constraint FK4B9075705E0AFEFE foreign key (classesid) references t_classes (id) |
生成的對應的關系模型如下圖:
對比SQL語句和關系模型,相應的表之間的關聯是通過外鍵來維護的,首先是創建兩張表,并指定表的主鍵,最后添加一對多的外鍵關聯關系。
2、基本操作
在對數據庫的操作無非是讀和寫兩種,修改也屬于寫的一種,接下來看看是如何向數據庫中寫入和讀取操作的。
(1)寫入數據:
寫入數據需要注意的是一對多的關系,所以在添加的時候需要添加多個學生類,另外由于在classes中添加了對應的set屬性,所以在添加Student對象時應該使用HashSet來添加,這樣既可實現一對多的關系,具體如下代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
public void testSave2(){ Session session= null ; try { session=HibernateUtils.getSession(); session.beginTransaction(); Student student1= new Student(); student1.setName( "zhangsan" ); session.save(student1); Student student2= new Student(); student2.setName( "lisi" ); session.save(student2); Classes classes= new Classes(); classes.setName( "ClassOne" ); Set students= new HashSet(); students.add(student1); students.add(student2); classes.setStudents(students); //可以成功保存數據 //但是會發出多余的update語句來維持關系,因為是一對多的原因 session.save(classes); session.getTransaction().commit(); } catch (Exception e){ e.printStackTrace(); session.getTransaction().rollback(); } finally { HibernateUtils.closeSession(session); } } |
那么運行上面的測試用例生成的對應的數據寫入到數據庫中后如下圖:
(2)讀取數據:
寫入操作相對簡單,只需要把所有加載的對象都添加到Transient狀態下,運行相應的方法就可以插入內容,但是對應的讀取操作就會稍微復雜點,因為需要迭代獲取所有的學生對象,所以這種一對多的關系效率并不很高,具體代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
package com.test.hibernate; import java.util.Iterator; import java.util.Set; import com.src.hibernate.*; import junit.framework.TestCase; import org.hibernate.Session; public class One2ManyTest extends TestCase { public void testLoad1(){ Session session= null ; try { session=HibernateUtils.getSession(); session.beginTransaction(); //獲取主鍵為5的班級信息 Classes classes=(Classes)session.load(Classes. class , 5 ); //打印班級信息 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()); } session.getTransaction().commit(); } catch (Exception e){ e.printStackTrace(); session.getTransaction().rollback(); } finally { HibernateUtils.closeSession(session); } } } |
生成的相應的語句及信息如下語句:
1
2
3
4
5
|
Hibernate: select classes0_.id as id1_0_, classes0_. name as name1_0_ from t_classes classes0_ where classes0_.id=? classes. name =ClassOne Hibernate: select students0_.classesid as classesid1_, students0_.id as id1_, students0_.id as id0_0_, students0_. name as name0_0_ from t_student students0_ where students0_.classesid=? student. name =lisi student. name =zhangsan |
二、一對多雙向關聯映射
這里繼續采用學生和班級作為示例,班級和學生之間是一對多的關系,一個班級中擁有多名學生,和上篇文章不同的是這里的關系是雙向的,也就是一的一端和多的一端同時維護關聯關系,所以它的對象圖如下:
對應的關系模型圖沒有太大的變化,因為它們之間的關系是雙向的,所以在關系模型中兩端同時維護關聯關系,映射到關系模型中如下圖所示:
在一對多的單向關聯中映射文件只需要在一的一端進行特殊配置就可以,使用<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
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<?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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<? 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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
2
3
4
5
6
|
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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值,代碼如下:
寫入后對應的數據庫視圖如下:
對比兩種寫入操作,因為兩個寫入的先后順序不同所以出現了不同的結果,但因為是雙向的關聯關系所以在寫入時并不會發生異常。
4、讀取操作
相對于寫入數據而言,讀取數據就變得很簡單了,因為是雙向的關聯所以數據的讀取也是雙向的,可以從任何一端讀取另一端的信息,如下代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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
2
3
4
5
|
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