JPA实体关系映射:@ManyToMany多对多关系、@OneToMany@ManyToOne一对多多对一关系和@OneToOne的深度实例解析。

本文由作者三汪首发于简书。


为什么要有实体关系映射

答:简化编程操作。把冗余的操作交给底层框架来处理。
例如,如果我要给一位新入学的学生添加一位新的老师。而这个老师又是新来的,在学生数据库与教师数据库中均不存在对应的数据。那么我需要先在教师数据库中保存新来的老师的数据,同时在学生数据库中保存新学生的数据,然后再给两者建立关联。
而如果我们使用了实体关系映射,我们只需要将该新教师实体交给该学生实体,然后保存该学生实体即可完成。

什么是多对多关系

多对多关系是关系数据库中两个表之间的一种关系, 该关系中第一个表中的一个行可以与第二个表中的一个或多个行相关。第二个表中的一个行也可以与第一个表中的一个或多个行相关。
如果我们通过学生与课程的关系来说明多对多关系:一位学生,会修多门课程;而一门课程,也会被多位学生修习。此时,双方的关系即为多对多关系。
拥有多对多关系的两个实体将会有一个中间表来记录两者之间的关联关系。
下面,我们来建立实体。

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>


以上。
希望我的文章对你能有所帮助。
我不能保证文中所有说法的百分百正确,但我能保证它们都是我的理解和感悟以及拒绝复制黏贴。
有什么意见、见解或疑惑,欢迎留言讨论。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容