本文由作者三汪首發(fā)于簡(jiǎn)書(shū)。
為什么要有實(shí)體關(guān)系映射
答:簡(jiǎn)化編程操作铺坞。把冗余的操作交給底層框架來(lái)處理起宽。
例如,如果我要給一位新入學(xué)的學(xué)生添加一位新的老師济榨。而這個(gè)老師又是新來(lái)的坯沪,在學(xué)生數(shù)據(jù)庫(kù)與教師數(shù)據(jù)庫(kù)中均不存在對(duì)應(yīng)的數(shù)據(jù)。那么我需要先在教師數(shù)據(jù)庫(kù)中保存新來(lái)的老師的數(shù)據(jù)擒滑,同時(shí)在學(xué)生數(shù)據(jù)庫(kù)中保存新學(xué)生的數(shù)據(jù)腐晾,然后再給兩者建立關(guān)聯(lián)。
而如果我們使用了實(shí)體關(guān)系映射丐一,我們只需要將該新教師實(shí)體交給該學(xué)生實(shí)體藻糖,然后保存該學(xué)生實(shí)體即可完成。
什么是多對(duì)多關(guān)系
多對(duì)多關(guān)系是關(guān)系數(shù)據(jù)庫(kù)中兩個(gè)表之間的一種關(guān)系库车, 該關(guān)系中第一個(gè)表中的一個(gè)行可以與第二個(gè)表中的一個(gè)或多個(gè)行相關(guān)巨柒。第二個(gè)表中的一個(gè)行也可以與第一個(gè)表中的一個(gè)或多個(gè)行相關(guān)。
如果我們通過(guò)學(xué)生與課程的關(guān)系來(lái)說(shuō)明多對(duì)多關(guān)系:一位學(xué)生柠衍,會(huì)修多門(mén)課程洋满;而一門(mén)課程,也會(huì)被多位學(xué)生修習(xí)珍坊。此時(shí)牺勾,雙方的關(guān)系即為多對(duì)多關(guān)系。
擁有多對(duì)多關(guān)系的兩個(gè)實(shí)體將會(huì)有一個(gè)中間表來(lái)記錄兩者之間的關(guān)聯(lián)關(guān)系垫蛆。
下面禽最,我們來(lái)建立實(shí)體腺怯。
Studnt實(shí)體
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實(shí)體
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注解說(shuō)明:
如代碼所示袱饭,在兩個(gè)實(shí)體中,我們都使用了@ManyToMany這一注解呛占。
這一注解表明虑乖,當(dāng)前實(shí)體為多對(duì)多關(guān)系的其中一端。
注解可以在Collection晾虑、Set疹味、List仅叫、Map上使用,我們可以根據(jù)業(yè)務(wù)需要選擇糙捺。
Collection類是Set和List的父類诫咱,在未確定使用Set或List時(shí)可使用;
Set集合中對(duì)象不能重復(fù)洪灯,并且是無(wú)序的;
List集合中的對(duì)象可以有重復(fù)坎缭,并且可以有排序;
Map集合是帶有key和value值的集合签钩。
同時(shí)掏呼,我們聲明的集合需要進(jìn)行初始化。
如Collection可以初始化為ArrayList或HashSet铅檩;
Set可以初始化為HashSet憎夷;
List可以初始化為ArrayList;
Map可以初始化為HashMap昧旨。
在注解中拾给,我們可以設(shè)置cascade(級(jí)聯(lián)關(guān)系),fetch(加載策略),mappedBy(聲明關(guān)系的維護(hù)方)等屬性兔沃。
關(guān)于級(jí)聯(lián)關(guān)系可以在我的這篇文章中了解: ==》戳這里
我們簡(jiǎn)要介紹一下mappedBy鸣戴。
mappedBy聲明于關(guān)系的被維護(hù)方,聲明的值為關(guān)系的維護(hù)方的關(guān)系對(duì)象屬性名粘拾。
在實(shí)例中窄锅,mappedBy被聲明于Course類中,其值為Student類中的Set對(duì)象"courses"缰雇。即入偷,Student為關(guān)系維護(hù)方,Course為被維護(hù)方械哟。
但是在實(shí)際操作中疏之,我發(fā)現(xiàn)其實(shí)被維護(hù)方于維護(hù)方的概念并不那么重要。被維護(hù)方也可以對(duì)雙方關(guān)系進(jìn)行維護(hù)暇咆。下面通過(guò)一組測(cè)試用例來(lái)進(jìn)行說(shuō)明锋爪。
(關(guān)于mappedBy,我又更新了一篇補(bǔ)遺,建議閱讀。閱讀時(shí)間3分鐘 ==》戳這里)
測(cè)試用例
/**
* 僅將被維護(hù)方對(duì)象添加進(jìn)維護(hù)方對(duì)象Set中
* 保存維護(hù)方對(duì)象
*/
@Test
public void 多對(duì)多插入1() {
Student s = new Student();
s.setSName("二狗");
Course c = new Course();
c.setCName("語(yǔ)文");
s.getCourses().add(c);
studentService.save(s);
}
/**
* 僅將維護(hù)方對(duì)象添加進(jìn)被維護(hù)方對(duì)象Set中
* 保存被維護(hù)方對(duì)象
*/
@Test
public void 多對(duì)多插入2() {
Student s = new Student();
s.setSName("三汪");
Course c = new Course();
c.setCName("英語(yǔ)");
c.getStudents().add(s);
courseService.save(c);
}
/**
* 將雙方對(duì)象均添加進(jìn)雙方Set中
* 保存被維護(hù)方對(duì)象
*/
@Test
public void 多對(duì)多插入3() {
Student s = new Student();
s.setSName("一晌");
Course c = new Course();
c.setCName("數(shù)學(xué)");
s.getCourses().add(c);
c.getStudents().add(s);
courseService.save(c);
}
/**
* 刪除維護(hù)方對(duì)象
*/
@Test
public void 多對(duì)多刪除1(){
Student s = studentService.findByName("二狗");
studentService.delete(s);
}
/**
* 刪除被維護(hù)方對(duì)象
*/
@Test
public void 多對(duì)多刪除2(){
//Course c = courseService.findByName("英語(yǔ)");
Course c = courseService.findByName("數(shù)學(xué)");
courseService.delete(c);
}
測(cè)試說(shuō)明及結(jié)果:
在上面的測(cè)試用例中爸业,我們進(jìn)行了三次不同的保存和三次不同的保存刪除操作(多對(duì)多刪除2中分別進(jìn)行了兩次刪除操作)其骄,分別對(duì)應(yīng)二狗:語(yǔ)文
,三汪:英語(yǔ)
扯旷,一晌:數(shù)學(xué)
三組數(shù)據(jù)拯爽。
- 第一組數(shù)據(jù)(僅將被維護(hù)方對(duì)象添加進(jìn)維護(hù)方對(duì)象Set中,對(duì)維護(hù)方對(duì)象的單獨(dú)保存和刪除):由于操作對(duì)象是維護(hù)方钧忽,成功地在student毯炮、course以及中間表student_courses中分別添加了數(shù)據(jù)并成功進(jìn)行了刪除逼肯。若將刪除對(duì)象換成被維護(hù)方,同樣能夠成功刪除桃煎。
- 第二組數(shù)據(jù)(僅將維護(hù)方對(duì)象添加進(jìn)被維護(hù)方對(duì)象Set中篮幢,對(duì)被維護(hù)方對(duì)象的單獨(dú)保存和刪除):操作對(duì)象在這里換成了被維護(hù)方。不負(fù)眾望为迈,出問(wèn)題了洲拇。保存的時(shí)候,student表和course表倒是都成功地插入了數(shù)據(jù)曲尸,但是中間表中赋续,并未產(chǎn)生對(duì)兩者數(shù)據(jù)的關(guān)聯(lián)。因此另患,在刪除的時(shí)候也只刪除了course中的數(shù)據(jù)纽乱。
- 第三組數(shù)據(jù)( 將雙方對(duì)象均添加進(jìn)雙方Set中,對(duì)被維護(hù)方對(duì)象進(jìn)行保存和刪除):操作對(duì)象是被維護(hù)方昆箕,操作結(jié)果與第一組相同鸦列。
由此可知,實(shí)際操作中鹏倘,只要中間表建立了關(guān)聯(lián)薯嗤,即使是注解定義的被維護(hù)方也是可以對(duì)雙方關(guān)系進(jìn)行維護(hù)的。
一對(duì)多纤泵、多對(duì)一與一對(duì)一關(guān)系的介紹
當(dāng)我們了解完多對(duì)多關(guān)系以后骆姐,再來(lái)了解這三種關(guān)系映射就簡(jiǎn)單了許多。原理與多對(duì)多關(guān)系都是相同的捏题,下面將簡(jiǎn)要介紹其不同之處玻褪。
一對(duì)多關(guān)系與多對(duì)一關(guān)系
- 一對(duì)多關(guān)系即數(shù)據(jù)庫(kù)中的一行數(shù)據(jù)關(guān)聯(lián)另一個(gè)數(shù)據(jù)庫(kù)中的多行關(guān)系。多對(duì)一與之相反公荧。
- 一對(duì)多與多對(duì)一關(guān)系也可能會(huì)有中間表關(guān)聯(lián)兩者带射。但是我們一般不建議使用中間表。使用mapperBy可以避免系統(tǒng)生成中間表(會(huì)在多的一方數(shù)據(jù)庫(kù)中增加一個(gè)字段記錄外鍵)循狰。
- 這兩個(gè)關(guān)系中的mappedBy一般聲明于一的一方窟社,即一的一方為被維護(hù)方。
聲明示例:
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<>();
//其余略
}
一對(duì)一關(guān)系
- 一對(duì)一關(guān)系即兩個(gè)數(shù)據(jù)庫(kù)中的數(shù)據(jù)一一對(duì)應(yīng)绪钥。
其他就沒(méi)有什么需要額外介紹的了灿里,原理與上面的是關(guān)系映射一樣的。
聲明示例:
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;
//其余略
}
單向與雙向關(guān)聯(lián)的簡(jiǎn)介
本文從頭到尾所有的示例昧识,使用的都是雙向關(guān)聯(lián)钠四。即在關(guān)聯(lián)雙方都進(jìn)行關(guān)聯(lián)聲明盗扒。而事實(shí)上跪楞,除了雙向關(guān)聯(lián)缀去,還有一種用法是單向關(guān)聯(lián)。即在關(guān)聯(lián)的其中一方進(jìn)行關(guān)聯(lián)甸祭。
下面進(jìn)行介紹(轉(zhuǎn)):
當(dāng)使用單向關(guān)聯(lián)時(shí)缕碎,由父類管理關(guān)聯(lián)關(guān)系,子類無(wú)法管理池户,而這時(shí)咏雌,父親知道自己的兒子,但是校焦,從兒子對(duì)象不知道父親是誰(shuí)赊抖。
單向關(guān)聯(lián)時(shí),只指定<one-to-many>
當(dāng)使用雙向關(guān)聯(lián)時(shí)寨典,關(guān)聯(lián)關(guān)系的管理可以通過(guò)inverse指定氛雪,這時(shí),兒子能清楚的知道自己的父親是誰(shuí)耸成。 雙向關(guān)聯(lián)時(shí)报亩,還要指定<many-to-one>
以上。
希望我的文章對(duì)你能有所幫助井氢。
我不能保證文中所有說(shuō)法的百分百正確弦追,但我能保證它們都是我的理解和感悟以及拒絕復(fù)制黏貼。
有什么意見(jiàn)花竞、見(jiàn)解或疑惑劲件,歡迎留言討論。