【问题标题】:Difference Between One-to-Many, Many-to-One and Many-to-Many?一对多、多对一和多对多的区别?
【发布时间】:2011-03-08 00:47:01
【问题描述】:

好的,所以这可能是一个微不足道的问题,但我无法想象和理解差异以及何时使用它们。我也有点不清楚单向和双向映射等概念如何影响一对多/多对多关系。我现在正在使用 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
}

那么在这种情况下,我需要什么样的映射?对这个特定示例的回答绝对值得赞赏,但我也非常想了解何时使用一对多和多对多以及何时使用连接表与连接列以及单向与双向。

【问题讨论】:

  • 看起来每个人都只回答一对多和多对多。看看我对一对多与多对一的回答。

标签: java hibernate orm many-to-many one-to-many


【解决方案1】:

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

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

One-to-manyMany-to-one 是一个视角问题UnidirectionalBidirectional 不会影响映射,但会影响您访问数据的方式。

  • Many-to-one 中,many 端将保留one 端的引用。一个很好的例子是“一个国家有城市”。在这种情况下,State 是一侧,City 是多侧。表格cities中会有一列state_id

单向中,Person 类将具有 List&lt;Skill&gt; skillsSkill 不会有 Person person。在双向中,两者 添加了属性,它允许您访问Person,给定一个 技能(即skill.person)。

  • One-to-Many 中,一侧将是我们的参考点。例如,“用户有地址”。在这种情况下,我们可能有三列 address_1_idaddress_2_idaddress_3_idlook up tablemulti column unique constraint on user_idaddress_id

单向中,User 将具有Address address双向Address 类中将有一个额外的List&lt;User&gt; users

  • Many-to-Many中,每一方的成员都可以持有对方任意数量的成员的引用。为此,使用了look up table。这方面的例子是医生和病人之间的关系。一个医生可以有很多病人,反之亦然。

【讨论】:

  • 这应该是公认的答案,其他大多数答案都错过了这个问题。
  • 第一个例子不正确。如果您有一个人,并且人有 @OneToMany 和技能,则该表 preson_skills 将对技能 ID 具有唯一约束。因此,一项技能只会映射到一个人。而且你不能提取s.persons,因为只有s.person
  • 实际上你描述的One-to-many关系是Many-to-many关系,因为person有很多skills的引用,但是skill没有保留对特定人的引用,很多persons可以有一个相同的参考skill。而您的Many-to-one 关系实际上是One-to-many,因为每个技能都只引用一个person,因为孩子只有一个母亲。
  • Person 拥有List&lt;Skill&gt; skills 时也是单向一对多示例,实际上是多对多,因为一个人可以拥有许多技能,而技能可以在许多List&lt;Skill&gt; skills 列表中。我想你想写“在单向Skill 类将有Person person”。
  • @mixel 我现在明白你的意思了,但我不认为List&lt;Skill&gt; skills 存在于Person 中的事实自动使关系@Many-to-many 因为在生成的架构上可能有一个@987654377 @ 约束person_id 使技能为一个人所有。有意义吗?
【解决方案2】:

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

  • 单向:Person 可以通过 Set 直接引用 Skills
  • 双向:每个“子”技能都有一个指向 人员(未在您的代码中显示)

Many-to-Many:一个人有很多技能,一个技能在人之间重复使用

  • 单向:Person 可以通过 Set 直接引用 Skills
  • 双向:一项技能有一组与之相关的人员。

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

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

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

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

【讨论】:

  • 澄清一下,任何关系在您的 BL 或 O/R 映射中都可能是单向或双向的(甚至彼此独立!)。
  • “爱”的例子只是澄清了它。 ManyToMany 是我的映射类型。
  • 超级。这很好地解释了它(并且在 OP 示例的上下文中)
  • 没有正确回答问题。你错过了多对一的部分。尽管一对多和多对一是感知问题,但此答案并未提及。
【解决方案3】:

1) 圆圈是实体/POJO/Beans

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

PK=主键,FK=外键

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

【讨论】:

  • 真的很喜欢它在两个方向上将对象图与表格联系起来。
  • 看书呆子,这就是 PROGRAMMER's HANDWRITING 的样子:D
  • 我知道你在这里做了什么。
【解决方案4】:

一对多

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

在关系数据库系统中,一对多表关系基于子表中的 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),用于同步双向关联的双方。

您应该在使用双向关联时提供这些方法,否则,您将面临very subtle state propagation issues 的风险。

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

一对一

一对一的表关系如下:

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

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

在上表图中,post_details 表中的id 列与postidPrimary 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 关联的标识符。

多对多

多对多表关系如下:

在关系数据库系统中,多对多表关系通过一个子表链接两个父表,该子表包含两个引用两个父表的Primary Key 列的Foreign 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 注释进行标记。在这种情况下,唯一的业务密钥是 the best candidate for equality checks
  5. Tag 实体中posts 关联的mappedBy 属性标志着,在这种双向关系中,Post 实体拥有该关联。这是必需的,因为只有一方可以拥有关系,并且更改只会从这一特定方传播到数据库。
  6. Set 是首选,因为使用 List@ManyToMany 效率较低。

【讨论】:

    【解决方案5】:

    看看这篇文章:Mapping Object Relationships

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

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

    【讨论】:

    • this occurs when the maximum of one multiplicity is one and the other is greater than onelolwut?
    【解决方案6】:

    我会这样解释:

    OneToOne - OneToOne 关系

    @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;
    

    【讨论】:

      【解决方案7】:

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

      
      
      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 但它也可以在没有...的情况下工作......

      【讨论】:

        【解决方案8】:

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

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

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

        class PersonSkill 
        {
            Person person;
            Skill skill;    
        }
        

        那你看到你有什么了吗?你有两个一对多的关系。 (在这种情况下,Person 可能有 PersonSkills 的集合,但不会有 Skills 的集合。)但是,有些人更喜欢使用多对多关系(在 Person 和 Skill 之间);这是有争议的。

        第四,如果你确实有双向关系(例如,不仅 Person 有一个 Skills 集合,而且 Skill 有一个 Persons 集合),NHibernate 在你的 BL 中强制执行双向为你;它仅出于持久性目的而理解关系的双向性。

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

        祝你好运!

        【讨论】:

          猜你喜欢
          • 2013-11-07
          • 2011-06-03
          • 1970-01-01
          • 1970-01-01
          • 2011-08-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多