本文由作者三汪首發于簡書。
為什么要有實體關系映射
答:簡化編程操作。把冗余的操作交給底層框架來處理。
例如,如果我要給一位新入學的學生添加一位新的老師。而這個老師又是新來的,在學生數據庫與教師數據庫中均不存在對應的數據。那么我需要先在教師數據庫中保存新來的老師的數據,同時在學生數據庫中保存新學生的數據,然后再給兩者建立關聯。
而如果我們使用了實體關系映射,我們只需要將該新教師實體交給該學生實體,然后保存該學生實體即可完成。
什么是多對多關系
多對多關系是關系數據庫中兩個表之間的一種關系, 該關系中第一個表中的一個行可以與第二個表中的一個或多個行相關。第二個表中的一個行也可以與第一個表中的一個或多個行相關。
如果我們通過學生與課程的關系來說明多對多關系:一位學生,會修多門課程;而一門課程,也會被多位學生修習。此時,雙方的關系即為多對多關系。
擁有多對多關系的兩個實體將會有一個中間表來記錄兩者之間的關聯關系。
下面,我們來建立實體。
Studnt實體
package com.wolfgy.domain;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import org.hibernate.annotations.GenericGenerator;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@NoArgsConstructor
@Getter
@Setter
public class Student {
@Id
@GeneratedValue(generator = "idGenerator")
@GenericGenerator(name = "idGenerator", strategy = "uuid")
private String id;
private String sName;
@ManyToMany(cascade=CascadeType.ALL,fetch=FetchType.LAZY)
private Set<Course> courses = new HashSet<>();
}
Course實體
package com.wolfgy.domain;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import org.hibernate.annotations.GenericGenerator;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@NoArgsConstructor
@Getter
@Setter
public class Course {
@Id
@GeneratedValue(generator = "idGenerator")
@GenericGenerator(name = "idGenerator", strategy = "uuid")
private String id;
private String cName;
@ManyToMany(cascade=CascadeType.ALL,fetch=FetchType.LAZY,mappedBy="courses")
private Set<Student> students= new HashSet<>();
}
@ManyToMany注解說明:
如代碼所示,在兩個實體中,我們都使用了@ManyToMany這一注解。
這一注解表明,當前實體為多對多關系的其中一端。
注解可以在Collection、Set、List、Map上使用,我們可以根據業務需要選擇。
Collection類是Set和List的父類,在未確定使用Set或List時可使用;
Set集合中對象不能重復,并且是無序的;
List集合中的對象可以有重復,并且可以有排序;
Map集合是帶有key和value值的集合。
同時,我們聲明的集合需要進行初始化。
如Collection可以初始化為ArrayList或HashSet;
Set可以初始化為HashSet;
List可以初始化為ArrayList;
Map可以初始化為HashMap。
在注解中,我們可以設置cascade(級聯關系),fetch(加載策略),mappedBy(聲明關系的維護方)等屬性。
關于級聯關系可以在我的這篇文章中了解: ==》戳這里
我們簡要介紹一下mappedBy。
mappedBy聲明于關系的被維護方,聲明的值為關系的維護方的關系對象屬性名。
在實例中,mappedBy被聲明于Course類中,其值為Student類中的Set對象"courses"。即,Student為關系維護方,Course為被維護方。
但是在實際操作中,我發現其實被維護方于維護方的概念并不那么重要。被維護方也可以對雙方關系進行維護。下面通過一組測試用例來進行說明。
(關于mappedBy,我又更新了一篇補遺,建議閱讀。閱讀時間3分鐘 ==》戳這里)
測試用例
/**
* 僅將被維護方對象添加進維護方對象Set中
* 保存維護方對象
*/
@Test
public void 多對多插入1() {
Student s = new Student();
s.setSName("二狗");
Course c = new Course();
c.setCName("語文");
s.getCourses().add(c);
studentService.save(s);
}
/**
* 僅將維護方對象添加進被維護方對象Set中
* 保存被維護方對象
*/
@Test
public void 多對多插入2() {
Student s = new Student();
s.setSName("三汪");
Course c = new Course();
c.setCName("英語");
c.getStudents().add(s);
courseService.save(c);
}
/**
* 將雙方對象均添加進雙方Set中
* 保存被維護方對象
*/
@Test
public void 多對多插入3() {
Student s = new Student();
s.setSName("一晌");
Course c = new Course();
c.setCName("數學");
s.getCourses().add(c);
c.getStudents().add(s);
courseService.save(c);
}
/**
* 刪除維護方對象
*/
@Test
public void 多對多刪除1(){
Student s = studentService.findByName("二狗");
studentService.delete(s);
}
/**
* 刪除被維護方對象
*/
@Test
public void 多對多刪除2(){
//Course c = courseService.findByName("英語");
Course c = courseService.findByName("數學");
courseService.delete(c);
}
測試說明及結果:
在上面的測試用例中,我們進行了三次不同的保存和三次不同的保存刪除操作(多對多刪除2中分別進行了兩次刪除操作),分別對應二狗:語文
,三汪:英語
,一晌:數學
三組數據。
- 第一組數據(僅將被維護方對象添加進維護方對象Set中,對維護方對象的單獨保存和刪除):由于操作對象是維護方,成功地在student、course以及中間表student_courses中分別添加了數據并成功進行了刪除。若將刪除對象換成被維護方,同樣能夠成功刪除。
- 第二組數據(僅將維護方對象添加進被維護方對象Set中,對被維護方對象的單獨保存和刪除):操作對象在這里換成了被維護方。不負眾望,出問題了。保存的時候,student表和course表倒是都成功地插入了數據,但是中間表中,并未產生對兩者數據的關聯。因此,在刪除的時候也只刪除了course中的數據。
- 第三組數據( 將雙方對象均添加進雙方Set中,對被維護方對象進行保存和刪除):操作對象是被維護方,操作結果與第一組相同。
由此可知,實際操作中,只要中間表建立了關聯,即使是注解定義的被維護方也是可以對雙方關系進行維護的。
一對多、多對一與一對一關系的介紹
當我們了解完多對多關系以后,再來了解這三種關系映射就簡單了許多。原理與多對多關系都是相同的,下面將簡要介紹其不同之處。
一對多關系與多對一關系
- 一對多關系即數據庫中的一行數據關聯另一個數據庫中的多行關系。多對一與之相反。
- 一對多與多對一關系也可能會有中間表關聯兩者。但是我們一般不建議使用中間表。使用mapperBy可以避免系統生成中間表(會在多的一方數據庫中增加一個字段記錄外鍵)。
- 這兩個關系中的mappedBy一般聲明于一的一方,即一的一方為被維護方。
聲明示例:
public class Student {
@ManyToOne(cascade=CascadeType.ALL,fetch=FetchType.LAZY)
private ClassEntity classEntity;
//其余略
}
public class ClassEntity {
@OneToMany(cascade=CascadeType.PERSIST,fetch=FetchType.LAZY,mappedBy="classEntity")
private Set<Student> students= new HashSet<>();
//其余略
}
一對一關系
- 一對一關系即兩個數據庫中的數據一一對應。
其他就沒有什么需要額外介紹的了,原理與上面的是關系映射一樣的。
聲明示例:
public class NewsResourceEntity{
@OneToOne(optional = false, cascade = CascadeType.MERGE)
private ResourceEntity resource;
//其余略
}
public class ResourceEntity {
@OneToOne(optional = true, cascade = CascadeType.ALL, fetch=FetchType.LAZY, mappedBy = "resource")
private NewsResourceEntity newsResource;
//其余略
}
單向與雙向關聯的簡介
本文從頭到尾所有的示例,使用的都是雙向關聯。即在關聯雙方都進行關聯聲明。而事實上,除了雙向關聯,還有一種用法是單向關聯。即在關聯的其中一方進行關聯。
下面進行介紹(轉):
當使用單向關聯時,由父類管理關聯關系,子類無法管理,而這時,父親知道自己的兒子,但是,從兒子對象不知道父親是誰。
單向關聯時,只指定<one-to-many>
當使用雙向關聯時,關聯關系的管理可以通過inverse指定,這時,兒子能清楚的知道自己的父親是誰。 雙向關聯時,還要指定<many-to-one>
以上。
希望我的文章對你能有所幫助。
我不能保證文中所有說法的百分百正確,但我能保證它們都是我的理解和感悟以及拒絕復制黏貼。
有什么意見、見解或疑惑,歡迎留言討論。