Java的Hibernate框架中一對多的單向和雙向關(guān)聯(lián)映射
一、一對多單向關(guān)聯(lián)映射
一對多關(guān)系的對象模型在日常生活中也經(jīng)??吹剑湍脤W(xué)生和班級來說,一個班級里有多個學(xué)生,所以班級和學(xué)生的關(guān)系是一對多的關(guān)系,映射到對象模型中,如下圖:

對象模型說明了這種一對多的關(guān)系是由一的一端來維護的,那么映射成關(guān)系模型就是一個班級字段下面會有多個學(xué)生,這樣就形成了一對多的關(guān)系,通過班級能夠查詢獲得學(xué)生信息,對應(yīng)的關(guān)系模型如下圖:

1、基本配置
有了對象模型接下來就讓它們映射為對應(yīng)的關(guān)系代碼,在進行關(guān)系映射時需要在一的一端添加<one-to-many>標簽,另外還需要在一的一端添加Set屬性,它支持延遲加載,然后在映射文件添加set標簽,并指明一對多的關(guān)系,這樣就能夠在一的一端查詢獲取多的一端。
Classes類及映射文件:
它是模型中最重要的一端,在該端需要添加對應(yīng)的set屬性,并在配置文件中添加set標簽,在set標簽中配置相應(yīng)的<one-to-many>對象,具體Classes.java對象代碼如下:
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屬性,但是只是說明了延遲加載的屬性,并沒有為屬性配置對應(yīng)的對象,屬性的對象是要在映射文件中來配置的,需要添加set標簽,并在set標簽中添加<one-to-many>標簽,具體如下代碼:
<?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>
對應(yīng)的Student對象中的代碼和映射文件不需要什么特殊的配置,只需要按照通常的寫法編寫即可,具體的配置方法不再詳述,很簡單。配置好后需要生成對應(yīng)的SQL語句,將對象模型轉(zhuǎn)化為關(guān)系模型時Hibernate生成相應(yīng)的語句如下:
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)
生成的對應(yīng)的關(guān)系模型如下圖:

對比SQL語句和關(guān)系模型,相應(yīng)的表之間的關(guān)聯(lián)是通過外鍵來維護的,首先是創(chuàng)建兩張表,并指定表的主鍵,最后添加一對多的外鍵關(guān)聯(lián)關(guān)系。
2、基本操作
在對數(shù)據(jù)庫的操作無非是讀和寫兩種,修改也屬于寫的一種,接下來看看是如何向數(shù)據(jù)庫中寫入和讀取操作的。
(1)寫入數(shù)據(jù):
寫入數(shù)據(jù)需要注意的是一對多的關(guān)系,所以在添加的時候需要添加多個學(xué)生類,另外由于在classes中添加了對應(yīng)的set屬性,所以在添加Student對象時應(yīng)該使用HashSet來添加,這樣既可實現(xiàn)一對多的關(guān)系,具體如下代碼:
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);
//可以成功保存數(shù)據(jù)
//但是會發(fā)出多余的update語句來維持關(guān)系,因為是一對多的原因
session.save(classes);
session.getTransaction().commit();
}catch(Exception e){
e.printStackTrace();
session.getTransaction().rollback();
}finally{
HibernateUtils.closeSession(session);
}
}
那么運行上面的測試用例生成的對應(yīng)的數(shù)據(jù)寫入到數(shù)據(jù)庫中后如下圖:

(2)讀取數(shù)據(jù):
寫入操作相對簡單,只需要把所有加載的對象都添加到Transient狀態(tài)下,運行相應(yīng)的方法就可以插入內(nèi)容,但是對應(yīng)的讀取操作就會稍微復(fù)雜點,因為需要迭代獲取所有的學(xué)生對象,所以這種一對多的關(guān)系效率并不很高,具體代碼如下:
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());
//設(shè)置學(xué)生集合,通過班級加載學(xué)生集合
Set students=classes.getStudents();
//迭代集合,打印集合中學(xué)生的信息
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);
}
}
}
生成的相應(yīng)的語句及信息如下語句:
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
二、一對多雙向關(guān)聯(lián)映射
這里繼續(xù)采用學(xué)生和班級作為示例,班級和學(xué)生之間是一對多的關(guān)系,一個班級中擁有多名學(xué)生,和上篇文章不同的是這里的關(guān)系是雙向的,也就是一的一端和多的一端同時維護關(guān)聯(lián)關(guān)系,所以它的對象圖如下:

對應(yīng)的關(guān)系模型圖沒有太大的變化,因為它們之間的關(guān)系是雙向的,所以在關(guān)系模型中兩端同時維護關(guān)聯(lián)關(guān)系,映射到關(guān)系模型中如下圖所示:

在一對多的單向關(guān)聯(lián)中映射文件只需要在一的一端進行特殊配置就可以,使用<one-to-many>配置,并在對象模型中使用set迭代器來設(shè)置外聯(lián)的對象模型,但是不同的是在雙向的關(guān)聯(lián)中需要在多的一端添加對應(yīng)的另一端的外鍵關(guān)聯(lián),這時候就必須在多的一端使用<many-to-one>的關(guān)聯(lián)關(guān)系來標明這種雙向性。
1、映射
這里還使用Classes和Student來做示例,在Classes一端的內(nèi)容和上文相同不會發(fā)生變換,但是多的一端Student的配置會發(fā)生變化,也就是在映射文件中需要添加<many-to-one>標簽。
Student.hbm.xml映射文件配置需要添加外鍵列<many-to-one>標簽,并且該列的名稱要和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-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屬性映射對應(yīng)了Student對象,所以在映射文件中需要添加set標簽來指示為對象模型中使用了set迭代器,具體配置如下代碼:
<?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、類
映射文件的配置是直接對應(yīng)著類來的,所以有了映射文件就能夠?qū)懗鱿鄳?yīng)的類,相同的有了類就能夠知道對應(yīng)的映射文件如何編寫,那來看看相應(yīng)的類代碼如何編寫。
Student.java類,需要在類中添加關(guān)聯(lián)的班級對象屬性,在加載Student時能獲得Classes的相關(guān)信息。
package com.src.hibernate;
public class Student {
//關(guān)聯(lián)的班級對象
private Classes classes;
public Classes getClasses() {
return classes;
}
public void setClasses(Classes classes) {
this.classes = classes;
}
//學(xué)生id
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
//學(xué)生姓名
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Classes.java文件具體代碼內(nèi)容見上篇文章,這里就不在詳述。
有了對象模型接下來生成關(guān)系模型,生成的SQL語句如下:
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、數(shù)據(jù)操作
建立表結(jié)構(gòu)后來編寫測試方法來驗證數(shù)據(jù)的操作,首先來看看數(shù)據(jù)的插入,向表結(jié)構(gòu)中插入數(shù)據(jù),寫入數(shù)據(jù)時會有兩種情況,一種是首先創(chuàng)建一個Classes對象,并將對象寫入到數(shù)據(jù)庫中,然后創(chuàng)建Student對象,在Classes對象中添加學(xué)生對象;另外一種是先創(chuàng)建學(xué)生對象,并將學(xué)生對象寫入數(shù)據(jù)庫中,然后創(chuàng)建Classes對象將學(xué)生對象加入到Classes對象中,這兩種類型的操作最后是不相同的,來對比下。
3.1 先寫班級后寫學(xué)生
先把班級寫入到數(shù)據(jù)庫中后,Classes對象進入了Transient狀態(tài),并在數(shù)據(jù)庫中有了一行,這時再寫Student對象,Student對象會查找對應(yīng)的Classes的主鍵將其寫入到表中,所以此時關(guān)系模型中的數(shù)據(jù)都是非空的,保存的代碼如下:
public void testSave(){
Session session=null;
try{
//創(chuàng)建session對象
session=HibernateUtils.getSession();
//開啟事務(wù)
session.beginTransaction();
//創(chuàng)建班級對象,將班級對象寫入到數(shù)據(jù)庫中
Classes classes=new Classes();
classes.setName("class");
session.save(classes);
//創(chuàng)建學(xué)生1對象,將學(xué)生對象寫入到數(shù)據(jù)庫中
Student student1=new Student();
student1.setName("zhangsan");
student1.setClasses(classes);
session.save(student1);
//創(chuàng)建學(xué)生2對象,將學(xué)生對象寫入到數(shù)據(jù)庫中
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);
}
}
對應(yīng)的寫入數(shù)據(jù)庫中的信息列表如下圖:

3.2 先寫學(xué)生后寫班級
先把學(xué)生寫入到數(shù)據(jù)庫中此時因為學(xué)生表需要獲取對應(yīng)的班級列的主鍵信息,但是因為班級信息轉(zhuǎn)化到Transient狀態(tài),所以在寫入學(xué)生信息時會有null值,代碼如下:
寫入后對應(yīng)的數(shù)據(jù)庫視圖如下:

對比兩種寫入操作,因為兩個寫入的先后順序不同所以出現(xiàn)了不同的結(jié)果,但因為是雙向的關(guān)聯(lián)關(guān)系所以在寫入時并不會發(fā)生異常。
4、讀取操作
相對于寫入數(shù)據(jù)而言,讀取數(shù)據(jù)就變得很簡單了,因為是雙向的關(guān)聯(lián)所以數(shù)據(jù)的讀取也是雙向的,可以從任何一端讀取另一端的信息,如下代碼:
public void testLoad1(){
Session session=null;
try{
session=HibernateUtils.getSession();
session.beginTransaction();
//通過班級讀取學(xué)生信息
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());
}
//通過學(xué)生信息讀取班級信息
Student stu=new Student();
stu=(Student)session.load(Student.class, 1);
System.out.println("通過學(xué)生加載班級信息Classes.id= "+stu.getClasses().getId());
session.getTransaction().commit();
}catch(Exception e){
e.printStackTrace();
session.getTransaction().rollback();
}finally{
HibernateUtils.closeSession(session);
}
}
運行上面的測試語句,生成的對應(yīng)的語句信息如下:
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
通過學(xué)生加載班級信息Classes.id= 1
- Hibernate雙向多對多映射關(guān)系配置代碼實例
- 詳解hibernate雙向多對多關(guān)聯(lián)映射XML與注解版
- Java的Hibernate框架中的雙向主鍵關(guān)聯(lián)與雙向外鍵關(guān)聯(lián)
- Hibernate一對多關(guān)聯(lián)雙向關(guān)聯(lián)代碼實現(xiàn)分享
- Hibernate組件映射代碼詳解
- Hibernate映射文件id的generator配置方法
- Hibernate使用hbm.xml配置映射關(guān)系解析
- 快速了解hibernate配置文件與映射文件
- Hibernate雙向一對一映射關(guān)系配置代碼實例
相關(guān)文章
Java編程實現(xiàn)基于TCP協(xié)議的Socket聊天室示例
這篇文章主要介紹了Java編程實現(xiàn)基于TCP協(xié)議的Socket聊天室,結(jié)合實例形式詳細分析了java基于TCP協(xié)議的Socket聊天室客戶端與服務(wù)器端相關(guān)實現(xiàn)與使用技巧,需要的朋友可以參考下2018-01-01
Sharding-Proxy分庫分表和數(shù)據(jù)加密使用場景分析
這篇文章主要介紹了Sharding-Proxy分庫分表和數(shù)據(jù)加密使用經(jīng)驗分享,通過場景模擬分析結(jié)合示例代碼給大家介紹的非常詳細,需要的朋友可以參考下2022-04-04

