一对多、多对一、多对多的区别?

好吧,这可能是一个微不足道的问题,但我很难想象和理解它们的区别,以及何时使用它们。我还不太清楚像单向映射和双向映射这样的概念如何影响一对多/多对多关系。我现在使用的是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
}

在这种情况下,会有什么样的映射呢?希望您能回答这个特定的示例,但我还想了解一下什么时候使用一对多和多对多,什么时候使用连接表和连接列,什么时候使用单向和双向。

182021 次浏览

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

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

*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.

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




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,但它也可能没有…

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

  • 单向:一个人可以通过它的集合直接引用技能
  • 双向:每个"child"技能都有一个指向 Person(在你的代码中没有显示)

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

  • 单向:一个人可以通过它的集合直接引用技能
  • 双向:一项技能有一组与之相关的人。

在一对多关系中,一个对象是“父对象”,一个是“子对象”。父元素控制子元素的存在。在多对多中,任何一种类型的存在都依赖于它们之外的东西(在更大的应用程序上下文中)。

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

令人困惑的是,多对多双向关系不需要是对称的!也就是说,一群人可以指向一项技能,但该技能不需要只与这些人相关。通常情况下是这样的,但这种对称性并不是必需的。以爱为例——它是双向的(“我爱”,“爱我”),但通常是不对称的(“我爱她,但她不爱我”)!

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

首先,阅读所有的小字。注意,NHibernate(因此,我认为Hibernate也是如此)关系映射与DB和对象图映射有一种有趣的对应关系。例如,一对一关系通常被实现为多对一关系。

其次,在我们告诉你应该如何写你的O/R图之前,我们也必须看到你的DB。特别是,一项技能可以被多人拥有吗?如果是这样,你有一个多对多关系;否则就是多对一。

第三,我不喜欢直接实现多对多关系,而是在您的域模型中对“连接表”建模。,将其视为一个实体,如下所示:

class PersonSkill
{
Person person;
Skill skill;
}

那你知道你有什么了吗?您有两个一对多的关系。(在本例中,Person可能有一个PersonSkills集合,但不会有一个Skills集合。)然而,有些人更喜欢使用多对多关系(人与技能之间);这是有争议的。

第四,如果你确实有双向关系(例如,不仅Person有一个技能集合,而且Skill也有一个Person集合),NHibernate会在你的BL中执行强制双向;它只理解关系的双向性以实现持久性。

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

好运!

1)圆圈代表实体/ pojo / bean

2) deg是degree在图中的缩写(边数)

PK=主键,FK=外键

注意度和边的名称之间的矛盾。Many对应degree=1, One对应degree >1。

一对多多多对一

看起来每个人都在回答One-to-manyMany-to-many:

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

__ABC0 vs Many-to-one是一个角度的问题UnidirectionalBidirectional不会影响映射,但会影响你如何访问数据。

  • Many-to-one中,many端将保留one端的引用。一个很好的例子就是“A State has cities”。在这种情况下,State是单面,而City是多面。在表cities中会有一个列state_id
单向中,Person类将有List<Skill> skills但是 Skill将没有Person person。在双向中,两者都是 属性被添加,它允许你访问给定的Person skill(即skill.person).

    One-to-Many中,一侧将是我们的参考点。例如,“A User has addresses”。在这种情况下,我们可能有三个列address_1_idaddress_2_idaddress_3_id,或者在user_id上有查表多列唯一约束address_id。< / >

单向中,User将具有Address address。< em >双向 将在Address类中有一个额外的List<User> users

  • Many-to-Many中,每一方的成员可以引用另一方的任意数量的成员。要实现这一点,使用查表。医生和病人之间的关系就是一个例子。一个医生可以有很多病人,反之亦然。

我会这样解释:

一对一-一对一关系

@OneToOne
Person person;


@OneToOne
Nose nose;

OneToMany -多对一关系

@OneToMany
Shepherd shepherd;


@ManyToOne
List<Sheep> sheeps;

多多多多-多多多多关系

@ManyToMany
List<Traveler> travelers;


@ManyToMany
List<Destination> destinations;

一对多

一对多表关系如下所示:

一对多

在关系数据库系统中,一对多表关系基于子表中的Foreign Key列关联两个表,该列引用父表中一条记录的Primary Key

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

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

@ManyToOne注释

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

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

    @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注释,这并不意味着它应该是所有一对多数据库关系的默认选项。

JPA集合的问题是,我们只能在它们的元素计数相当低时使用它们。

映射@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关联效率低。

一对一的

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

一对一

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

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

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

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

使用JPA @OneToOne@MapsId注释

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

映射是这样的:

@Entity(name = "PostDetails")
@Table(name = "post_details")
public class 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
}

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

多对多

多对多表关系如下所示:

多对多

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

在上面的图表中,post_tag表中的post_id列与post表id Primary Key列也有Foreign Key关系:

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

并且,post_tag表中的tag_id列与tag表id Primary Key列具有Foreign 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 getClass().hashCode();
}
}


@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. Post实体中的tags关联只定义了PERSISTMERGE级联类型。REMOVE实体状态转换对于@ManyToMany JPA关联没有任何意义,因为它可能会触发链式删除,最终清除关联的双方。
  2. 如果使用双向关联,则必须使用添加/删除实用程序方法,以便确保关联的双方是同步的。
  3. Post实体使用实体标识符来表示相等,因为它缺乏任何唯一的业务键。只要确保它在所有实体状态转换中保持一致,就可以使用相等的实体标识符。
  4. Tag实体有一个唯一的业务键,用hibernate特有的@NaturalId注释标记。在这种情况下,唯一的业务键是平等检查的最佳候选人
  5. Tag实体中posts关联的mappedBy属性标志着,在这种双向关系中,Post实体拥有该关联。这是必需的,因为只有一方可以拥有一个关系,并且更改仅从这一方传播到数据库。
  6. Set是首选,因为使用List@ManyToMany效率较低。