一对多,多对一和多对多的区别?


144

好的,所以这可能是一个琐碎的问题,但是我在可视化和理解差异以及何时使用它们方面遇到困难。对于诸如单向和双向映射之类的概念如何影响一对多/多对多关系,我也还不清楚。我现在正在使用Hibernate,因此任何与ORM相关的解释都将有所帮助。

举例来说,我有以下设置:

public class Person{
    private Long personId;
    private Set<Skill> skills;
    //Getters and setters
}

public class Skill{
    private Long skillId;
    private String skillName;
    //Getters and setters
}

那么在这种情况下,我将进行哪种映射?对于这个特定示例的答案肯定会受到赞赏,但我也确实希望获得何时使用一对多和多对多以及何时使用联接表与联接列以及单向与双向的概述。


11
似乎每个人都只回答一对多与多对多。看看我对一对多与多对一的答案。
2013年

Answers:


165

一对多:一个人有很多技能,一个技能不会在一个人之间重复使用

  • 单向性:一个人可以通过其设置直接参考技能
  • 双向的:每个“子级”技能都有一个指向人员的指针(代码中未显示)

多对多:一个人有很多技能,一种技能在人与人之间重复使用

  • 单向性:一个人可以通过其设置直接参考技能
  • 双向:一项技能具有一组与此相关的人。

在一对多关系中,一个对象是“父”对象,一个对象是“子”对象。父母控制孩子的存在。在多对多中,这两种类型的存在都依赖于两者之外的某种东西(在较大的应用程序上下文中)。

您的主题(领域)应决定该关系是一对多还是多对多-但是,我发现使关系是单向还是双向是一种权衡内存,处理,性能的工程决策。等

令人困惑的是,多对多双向关系不必是对称的!也就是说,一堆人可以指向一项技能,但该技能不必仅与那些人相关。通常会,但不是必须这样的对称。以爱情为例-它是双向的(“我爱”,“我爱”),但经常是不对称的(“我爱她,但她不爱我”)!

所有这些都得到了Hibernate和JPA的良好支持。只要记住,在管理双向多对多关系时,Hibernate或任何其他ORM都没有维护对称性……这完全取决于应用程序。


为了明确起见,在您的BL或O / R映射中,任何关系都可以是单向或双向的(彼此独立,甚至独立!)。
jyoungdev

4
“ LOVE”示例只是对其进行了澄清。ManyToMany是我的映射类型。
阿卜杜拉·汗

1
超。这很好地解释了这一点(并以OP为例)
Anupam

1
没有正确回答问题。您会错过很多部分。尽管一对多和一对一是一个感知问题,但是这个答案没有提及。
Manzur Alahi

248

似乎每个人都在回答One-to-manyvs . Many-to-many

之间的区别One-to-manyMany-to-oneMany-to-Many为:

One-to-manyvs Many-to-one是一个观点问题Unidirectionalvs Bidirectional不会影响映射,但会影响您访问数据的方式。

  • Many-to-onemany侧将保持基准one侧。一个很好的例子是“一个国家有城市”。在这种情况下State,一方面City是多方面。state_id表格中将有一列cities

单向中Person阶级将有List<Skill> skillsSkill不会有Person person。在双向中,两个属性都被添加,并且它允许您访问Person给定的技能(即skill.person)。

  • One-to-Many一侧将是我们的参考点。例如,“用户有地址”。在这种情况下,我们可能有三列address_1_idaddress_2_id以及address_3_id查询表与唯一约束user_idaddress_id

单向中,a User将具有Address address双向List<User> usersAddress该类中另辟 additional

  • Many-to-Many每一方的成员中可以保持引用另一方任意数目的成员。为了实现这一点,使用了查找表。例如,医患之间的关系。医生可以有很多病人,反之亦然。

27
这应该是公认的答案,其他大多数答案都错过了这个问题。
arg20

第一个例子是不正确的。如果您有一个Person且一个人的Skills具有@OneToMany,则该表preson_skills将对skill_id具有唯一约束。因此,一项技能只能映射到一个人。你不能提取s.persons,因为只有s.person
ИванНиколайчук

One-to-many正如您所描述的,Many-to-many关系实际上是一种关系,因为person它具有对许多人的引用,skillsskill并不保留对特定人的引用,并且许多人persons可以具有相同的引用skill。与您的Many-to-one关系实际上是One-to-many因为每项技能仅涉及一项技能,person因为孩子只有一名母亲。
mixel

@mixel您的评论最终使我重写了大部分答案。请再次检查!谢谢
Alexander Suraphel

您可以通过“一侧”的意义区分一对多和多对一。在两个示例中,“用户”都是最重要的实体。因此,当它位于“一个”(用户和技能,skills具有person_id)时,您可以将其称为“一对多”,而当它位于“许多”一侧(用户和地址,users具有address_id)时,则可以将其称为“多对一” 。但是从结构上讲,这两种情况是相同的,称为一对多。
mixel

38

1)圆圈是实体/ POJO / Bean

2)deg是度的缩写,如图表(边数)所示

PK =主键,FK =外键

请注意程度和边的名称之间的矛盾。许多对应于度= 1,而一个对应于度> 1。

一对多多对一图示


1
真的很喜欢它如何在两个方向上将对象图与表联系起来。
德米特里·明科夫斯基

3
看书呆子,这是怎么了程序员的 笔迹看起来像:d
Mehraj马利克

我看你在这里做什么。
Nick Gallimore

8

看一下这篇文章:映射对象关系

映射时需要考虑两类对象关系。第一类基于多重性,它包括三种类型:

*One-to-one relationships.  This is a relationship where the maximums of each of its multiplicities is one, an example of which is holds relationship between Employee and Position in Figure 11.  An employee holds one and only one position and a position may be held by one employee (some positions go unfilled).
*One-to-many relationships. Also known as a many-to-one relationship, this occurs when the maximum of one multiplicity is one and the other is greater than one.  An example is the works in relationship between Employee and Division.  An employee works in one division and any given division has one or more employees working in it.
*Many-to-many relationships. This is a relationship where the maximum of both multiplicities is greater than one, an example of which is the assigned relationship between Employee and Task.  An employee is assigned one or more tasks and each task is assigned to zero or more employees. 

第二类基于方向性,它包含两种类型,单向关系和双向关系。

*Uni-directional relationships.  A uni-directional relationship when an object knows about the object(s) it is related to but the other object(s) do not know of the original object.  An example of which is the holds relationship between Employee and Position in Figure 11, indicated by the line with an open arrowhead on it.  Employee objects know about the position that they hold, but Position objects do not know which employee holds it (there was no requirement to do so).  As you will soon see, uni-directional relationships are easier to implement than bi-directional relationships.
*Bi-directional relationships.  A bi-directional relationship exists when the objects on both end of the relationship know of each other, an example of which is the works in relationship between Employee and Division.  Employee objects know what division they work in and Division objects know what employees work in them. 

2
this occurs when the maximum of one multiplicity is one and the other is greater than one哈哈?
serg 2010年

3

这是一个非常常见的问题,因此此答案基于我在博客上写的这篇文章

一对多

一对多表关系如下所示:

一对多

在关系数据库系统中,一对多表关系基于Foreign Key子级中引用Primary Key父表行的的列链接两个表。

在上面的表图post_id中,post_comment表中的列Foreign Keypost表id Primary Key列具有关系:

ALTER TABLE
    post_comment
ADD CONSTRAINT
    fk_post_comment_post_id
FOREIGN KEY (post_id) REFERENCES post

@ManyToOne批注

映射一对多表关系的最佳方法是使用@ManyToOne注释。

在我们的例子中,子实体使用注释PostComment映射post_id外键列@ManyToOne

@Entity(name = "PostComment")
@Table(name = "post_comment")
public class PostComment {

    @Id
    @GeneratedValue
    private Long id;

    private String review;

    @ManyToOne(fetch = FetchType.LAZY)
    private Post post;

}

使用JPA @OneToMany批注

仅仅因为您可以选择使用@OneToMany批注,并不意味着对于每个一对多的数据库关系,这应该是默认选项。集合的问题在于我们只能在子记录的数量相当有限的情况下使用它们。

映射@OneToMany关联的最佳方法是依赖于@ManyToOne一侧传播所有实体状态更改:

@Entity(name = "Post")
@Table(name = "post")
public class Post {

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    @OneToMany(
        mappedBy = "post", 
        cascade = CascadeType.ALL, 
        orphanRemoval = true
    )
    private List<PostComment> comments = new ArrayList<>();

    //Constructors, getters and setters removed for brevity

    public void addComment(PostComment comment) {
        comments.add(comment);
        comment.setPost(this);
    }

    public void removeComment(PostComment comment) {
        comments.remove(comment);
        comment.setPost(null);
    }
}

父实体Post拥有两个实用程序方法(例如addCommentremoveComment),用于同步双向关联的双方。每当您使用双向关联时,都应始终提供这些方法,否则会冒着非常细微的状态传播问题的风险。

@OneToMany避免单向关联,因为它比使用@ManyToOne双向@OneToMany关联效率低。

有关映射@OneToMany与JPA和Hibernate关系的最佳方法的更多详细信息,请参阅本文

一对一

一对一的表关系如下所示:

一对一

在关系数据库系统中,一对一表关系基于Primary Key子级中的列链接两个表,该列也是父级表行的Foreign Key引用Primary Key

因此,可以说子表Primary Key与父表共享。

在上面的表图id中,post_details表中的列也Foreign Keypostid Primary Key列有关系:

ALTER TABLE
    post_details
ADD CONSTRAINT
    fk_post_details_id
FOREIGN KEY (id) REFERENCES post

将JPA @OneToOne@MapsId注释一起使用

映射@OneToOne关系的最佳方法是使用@MapsId。这样,您甚至不需要双向关联,因为您始终可以PostDetails使用Post实体标识符来获取实体。

映射如下所示:

[代码语言=“ java”] @Entity(name =“ PostDetails”)@Table(name =“ post_details”)公共类PostDetails {

@Id
private Long id;

@Column(name = "created_on")
private Date createdOn;

@Column(name = "created_by")
private String createdBy;

@OneToOne(fetch = FetchType.LAZY)
@MapsId
@JoinColumn(name = "id")
private Post post;

public PostDetails() {}

public PostDetails(String createdBy) {
    createdOn = new Date();
    this.createdBy = createdBy;
}

//Getters and setters omitted for brevity

} [/ code]

这样,该id属性既可以用作主键又可以用作外键。您会注意到,该@Id列不再使用@GeneratedValue注释,因为该标识符已用post关联的标识符填充。

有关映射@OneToOne与JPA和Hibernate关系的最佳方法的更多详细信息,请参阅本文

多对多

多对多表关系如下所示:

多对多

在关系数据库系统中,多对多表关系通过子表链接两个父表,该子表包含Foreign Key引用Primary Key两个父表的列的两列。

在上面的表图post_id中,post_tag表中的列也Foreign Keypost表ID Primary Key列有关系:

ALTER TABLE
    post_tag
ADD CONSTRAINT
    fk_post_tag_post_id
FOREIGN KEY (post_id) REFERENCES post

而且,tag_id在列post_tag表中有一个Foreign Key与关系tag表ID Primary Key列:

ALTER TABLE
    post_tag
ADD CONSTRAINT
    fk_post_tag_tag_id
FOREIGN KEY (tag_id) REFERENCES tag

使用JPA @ManyToMany映射

这是您可以many-to-many通过JPA和Hibernate 映射表关系的方法:

@Entity(name = "Post")
@Table(name = "post")
public class Post {

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    @ManyToMany(cascade = { 
        CascadeType.PERSIST, 
        CascadeType.MERGE
    })
    @JoinTable(name = "post_tag",
        joinColumns = @JoinColumn(name = "post_id"),
        inverseJoinColumns = @JoinColumn(name = "tag_id")
    )
    private Set<Tag> tags = new HashSet<>();

    //Getters and setters ommitted for brevity

    public void addTag(Tag tag) {
        tags.add(tag);
        tag.getPosts().add(this);
    }

    public void removeTag(Tag tag) {
        tags.remove(tag);
        tag.getPosts().remove(this);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Post)) return false;
        return id != null && id.equals(((Post) o).getId());
    }

    @Override
    public int hashCode() {
        return 31;
    }
}

@Entity(name = "Tag")
@Table(name = "tag")
public class Tag {

    @Id
    @GeneratedValue
    private Long id;

    @NaturalId
    private String name;

    @ManyToMany(mappedBy = "tags")
    private Set<Post> posts = new HashSet<>();

    //Getters and setters ommitted for brevity

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Tag tag = (Tag) o;
        return Objects.equals(name, tag.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
}
  1. 实体中的tags关联Post仅定义PERSIST和和MERGE级联类型。如本文所述,对于JPA关联,REMOVE 实体状态转换没有任何意义,@ManyToMany因为它可能触发链删除,最终删除关联的两面。
  2. 本文所述,如果您使用双向关联,则添加/删除实用程序方法是强制性的,这样您可以确保关联的两侧同步。
  3. Post实体使用的相等实体标识符,因为它没有任何独特的业务重点。如本文所述,只要确保实体标识符在所有实体状态转换之间保持一致,就可以使用实体标识符进行相等性处理
  4. Tag实体具有唯一的业务密钥,该密钥带有特定于Hibernate的@NaturalId注释。在这种情况下,唯一的业务密钥是进行平等检查的最佳人选
  5. 实体中关联的mappedBy属性标记为,在这种双向关系中,实体拥有关联。这是必需的,因为只有一方可以拥有关系,并且更改仅从该特定方传播到数据库。postsTagPost
  6. Set是优选的,如使用List具有@ManyToMany效率较低。

有关映射@ManyToMany与JPA和Hibernate关系的最佳方法的更多详细信息,请参阅本文


1

这可能需要如下的多对多关系



public class Person{

    private Long personId;
    @manytomany

    private Set skills;
    //Getters and setters
}

public class Skill{
    private Long skillId;
    private String skillName;
    @manyToMany(MappedBy="skills,targetClass="Person")
    private Set persons; // (people would not be a good convenion)
    //Getters and setters
}

您可能需要定义一个joinTable + JoinColumn,但也可以不使用...


1

我会这样解释:

一对一-一对一关系

@OneToOne
Person person;

@OneToOne
Nose nose;

OneToMany-ManyToOne关系

@OneToMany
Shepherd> shepherd;

@ManyToOne
List<Sheep> sheeps;

ManyToMany-ManyToMany关系

@ManyToMany
List<Traveler> travelers;

@ManyToMany
List<Destination> destinations;

0

首先,请阅读所有详细信息。请注意,NHibernate(因此,我也假设为Hibernate)关系映射与DB和对象图映射之间存在有趣的对应关系。例如,一对一关系通常被实现为多对一关系。

其次,在告诉您如何编写O / R映射之前,我们还必须查看您的数据库。特别是,一个人可以同时拥有多个技能吗?如果是这样,则您具有多对多关系;否则,它是多对一的。

第三,我不希望直接实现多对多关系,而应该在您的域模型中对“联接表”进行建模-即,将其视为一个实体,如下所示:

class PersonSkill 
{
    Person person;
    Skill skill;    
}

那你看到你有什么吗?您有两个一对多的关系。(在这种情况下,“人”可能具有“人的技能”的集合,而不会具有“技能”的集合。)但是,有些人会更喜欢使用“多对多”关系(“人”与“技能”之间);这是有争议的。

第四,如果你有双向关系(例如,不仅人体有一定技能的集合,而且,有技能的人的集合),NHibernate的并没有强制执行的双向您的BL你; 它仅出于持久性目的而了解关系的双向性。

第五,在NHibernate(我假设为Hibernate)中正确使用多对一要比一对多(集合映射)容易得多。

祝好运!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.