Разница между «один ко многим», «многие к одному» и «многие ко многим»?

156

Хорошо, это, вероятно, тривиальный вопрос, но у меня проблемы с визуализацией и пониманием различий и того, когда их использовать. Мне также немного неясно, как такие концепции, как однонаправленное и двунаправленное сопоставление, влияют на отношения «один-ко-многим / многие-ко-многим». Я использую 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
}

Итак, какое отображение у меня было бы в этом случае? Ответы на этот конкретный пример, безусловно, приветствуются, но мне также очень хотелось бы получить обзор того, когда использовать один-ко-многим и многие-ко-многим, а когда использовать таблицу соединений вместо столбца соединения и однонаправленную или двунаправленную.

Ян Даллас
источник
12
Похоже, что все отвечают только «один ко многим» против «многие ко многим». Посмотрите мой ответ «Один ко многим» против «Многие к одному».
Александр Сурафель

Ответы:

177

Один ко многим : у одного человека много навыков, навык не используется повторно между людьми

  • Однонаправленный : человек может напрямую ссылаться на навыки через свой набор.
  • Двунаправленный : у каждого «дочернего» навыка есть единственный указатель на человека (который не отображается в вашем коде).

Многие-ко-многим : у одного человека много навыков, навык повторно используется между людьми

  • Однонаправленный : человек может напрямую ссылаться на навыки через свой набор.
  • Двунаправленный : Навык имеет набор лиц, которые связаны с ним.

В отношениях «один ко многим» один объект является «родительским», а другой - «дочерним». Родитель контролирует существование ребенка. В случае "многие-ко-многим" существование любого типа зависит от чего-то вне их обоих (в более широком контексте приложения).

Ваш предмет (домен) должен диктовать, являются ли отношения один-ко-многим или многие-ко-многим - однако я считаю, что создание отношения однонаправленного или двунаправленного - это инженерное решение, которое требует обмена памяти, обработки и производительности. , так далее.

Что может сбивать с толку, так это то, что двунаправленное отношение «многие ко многим» не обязательно должно быть симметричным! То есть группа людей может указать на навык, но навык не обязательно должен относиться только к этим людям. Обычно это так, но такая симметрия не является обязательной. Возьмем, к примеру, любовь - она ​​двунаправленная («Я люблю», «Любит-меня»), но часто асимметрична («Я люблю ее, но она не любит меня»)!

Все это хорошо поддерживается Hibernate и JPA. Просто помните, что Hibernate или любой другой ORM не заботится о поддержании симметрии при управлении двунаправленными отношениями «многие-ко-многим» ... это все зависит от приложения.

HDave
источник
Чтобы прояснить, любые отношения могут быть однонаправленными или двунаправленными в вашем BL или в вашем отображении O / R (независимо друг от друга, даже!).
jyoungdev
4
Пример «ЛЮБОВЬ» только прояснил это. ManyToMany - это мой тип отображения.
Абдулла Хан
1
Супер. Это так хорошо объясняет (и в контексте примера OP)
Anupam
1
Не отвечает на вопрос должным образом. вы упускаете из виду многие из них. хотя один ко многим и многие к одному - это вопрос восприятия, в этом ответе об этом не упоминается.
Манзур Алахи,
264

Похоже, все отвечают One-to-manyvs Many-to-many.:

Разница между One-to-many, Many-to-oneи Many-to-Manyзаключается в следующем:

One-to-manyvs Many-to-one- это вопрос перспективы . Unidirectionalvs Bidirectionalне повлияет на сопоставление, но повлияет на то, как вы можете получить доступ к своим данным.

  • В Many-to-oneна manyстороне будет держать ссылку в oneстороне. Хороший пример - «В государстве есть города». В данном случае Stateэто одна сторона, а другая Cityсторона. В state_idтаблице будет столбец cities.

В однонаправленной , Personкласс будет иметь , List<Skill> skillsно Skillне будет иметь Person person. В двунаправленном режиме добавляются оба свойства, что позволяет вам получить доступ к Personзаданному навыку (т.е. skill.person).

  • С One-to-Manyодной стороны будет наша точка отсчета. Например, «У пользователя есть адреса». В этом случае мы могли бы иметь три колонки address_1_id, address_2_idи address_3_idили посмотреть таблицу с уникальным ограничением на user_idи address_id.

В однонаправленном , Userбудет иметь Address address. Двунаправленный будет иметь дополнительный List<User> usersв Addressклассе.

  • В Many-to-Manyчленах каждой партии может содержаться ссылка на произвольное количество членов другой стороны. Для этого используется справочная таблица . Примером этого являются отношения между врачами и пациентами. У врача может быть много пациентов и наоборот.
Александр Сурафель
источник
27
это должен быть принятый ответ, в большинстве других ответов вопрос отсутствует.
arg20
Первый пример неверен. Если у вас есть человек, а у человека есть @OneToMany с навыками, эта таблица preson_skills будет иметь уникальное ограничение на skill_id. Таким образом, один навык может быть назначен только одному человеку. А добывать нельзя s.persons, так как есть толькоs.person
Иван Николайчук
На самом деле One-to-manyотношения, как вы описываете, являются Many-to-manyотношениями, потому что personимеют ссылку на многие, skillsно skillне содержат ссылки на конкретного человека, и многие personsмогут иметь ссылку на то же самое skill. И ваши Many-to-oneотношения на самом деле связаны с One-to-manyтем, что каждый навык имеет отношение только к одному, personпоскольку у ребенка только одна мать.
mixel 06
1
Также однонаправленный один-ко-многим пример , когда Personесть List<Skill> skillsна самом деле многие-ко-многим , потому что человек может иметь много навыков и навыков может быть во многих List<Skill> skillsсписках. Думаю, вы хотели написать «В однонаправленном Skillклассе придется Person person».
mixel 01
1
@mixel Теперь я понял вашу точку зрения, но я не думаю, что тот факт, что List<Skill> skillsсуществует, Personавтоматически устанавливает отношения, @Many-to-manyпотому что в сгенерированной схеме может быть uniqueограничение на то, person_idчтобы умение принадлежало одному человеку. Имеет смысл?
Александр Сурафель 02
39

1) Кружки - это Entities / POJOs / Beans

2) deg - сокращение от степени, как в графах (количество ребер)

PK = первичный ключ, FK = внешний ключ

Обратите внимание на противоречие между степенью и названием стороны. Многие соответствуют степени = 1, а Один - степени> 1.

Иллюстрация "один ко многим", многие к одному

Джегедус
источник
1
Мне действительно нравится, как он связывает граф объектов с таблицами в обоих направлениях.
Дмитрий Минковский
6
Послушайте, ботаники , вот как выглядит РУКОВОДСТВО ПРОГРАММАТОРА : D
Мехрадж Малик
Я вижу, что вы здесь сделали.
Ник
9

Взгляните на эту статью: Сопоставление отношений объектов

Есть две категории объектных отношений, которые необходимо учитывать при отображении. Первая категория основана на множественности и включает три типа:

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

Это очень распространенный вопрос, поэтому этот ответ основан на статье, которую я написал в своем блоге.

Один ко многим

Отношение таблиц "один ко многим" выглядит так:

Один ко многим

В системе реляционной базы данных отношение таблиц «один ко многим» связывает две таблицы на основе Foreign Keyстолбца в дочерней таблице, ссылающегося на Primary Keyодну запись в родительской таблице.

На диаграмме таблицы выше, post_idстолбец в post_commentтаблице имеет Foreign Keyотношения с postидентификатор таблицы Primary Keyстолбца:

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

@ManyToOne аннотация

В JPA лучший способ отобразить связь таблицы «один-ко-многим» - использовать @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;
        
    }

Использование @OneToManyаннотации JPA

Тот факт, что у вас есть возможность использовать @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объект имеет два служебных метода (например, addCommentи removeComment), которые используются для синхронизации обеих сторон двунаправленной ассоциации.

Эти методы следует предоставлять всякий раз, когда вы работаете с двунаправленной ассоциацией, поскольку в противном случае вы рискуете очень тонкими проблемами распространения состояния .

@OneToManyСледует избегать однонаправленной ассоциации, поскольку она менее эффективна, чем использование @ManyToOneили двунаправленная @OneToManyассоциация.

Для получения дополнительных сведений о наилучшем способе сопоставления @OneToManyотношений с JPA и Hibernate ознакомьтесь с этой статьей .

Один к одному

Связь таблицы один-к-одному выглядит следующим образом:

Один к одному

В системе реляционной базы данных отношение таблицы один к одному связывает две таблицы на основе Primary Keyстолбца в дочернем элементе, который также является Foreign Keyссылкой на Primary Keyстроку родительской таблицы.

Следовательно, мы можем сказать, что дочерняя таблица имеет общий Primary Keyс родительской таблицей.

На диаграмме таблицы выше idстолбец в post_detailsтаблице также Foreign Keyсвязан со столбцом postтаблицы id Primary Key:

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

Использование JPA @OneToOne с @MapsIdаннотациями

Лучший способ обозначить @OneToOneотношения - использовать @MapsId. Таким образом, вам даже не потребуется двунаправленная ассоциация, поскольку вы всегда можете получитьPostDetails объект, используя Postидентификатор объекта.

Отображение выглядит так:

@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 ассоциации.

Для получения дополнительных сведений о лучшем способе сопоставления @OneToOneотношений с JPA и Hibernate ознакомьтесь с этой статьей. .

Многие-ко-многим

Отношения таблицы многие-ко-многим выглядят следующим образом:

Многие-ко-многим

В системе реляционной базы данных отношение таблиц «многие ко многим» связывает две родительские таблицы через дочернюю таблицу, которая содержит два Foreign Keyстолбца, ссылающихся на Primary Keyстолбцы двух родительских таблиц.

На диаграмме таблицы выше post_idстолбец в post_tagтаблице также Foreign Keyсвязан со столбцом postидентификатора таблицы 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отношения с tagID таблицы Primary Keyстолбца:

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

Использование @ManyToManyсопоставления JPA

Вот как вы можете сопоставить 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каскадные типов. Как объясняется в этой статье , изменение REMOVE состояния объекта не имеет смысла для@ManyToMany ассоциации JPA, поскольку он может вызвать удаление цепочки, которое в конечном итоге приведет к стиранию обеих сторон ассоциации.
  2. Как объясняется в этой статье , служебные методы добавления / удаления являются обязательными, если вы используете двунаправленные ассоциации, чтобы вы могли убедиться, что обе стороны ассоциации синхронизированы.
  3. PostОбъект использует идентификатор сущности для равенства , поскольку в нем отсутствует какой - либо уникальный бизнес - ключ. Как объясняется в этой статье , вы можете использовать идентификатор объекта для равенства, если убедитесь, что он остается согласованным при всех переходах состояния объекта .
  4. TagОбъект имеет уникальный бизнес - ключ , который отмечается с Hibernate-специфической @NaturalIdаннотацией. В этом случае уникальный бизнес-ключ является лучшим кандидатом для проверки равенства .
  5. mappedByАтрибут postsассоциации в Tagотметках сущностей , что в этом двунаправленной связи, то Postпредприятие имеет ассоциацию. Это необходимо, поскольку только одна сторона может владеть отношением, и изменения распространяются в базу данных только с этой конкретной стороны.
  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 - отношения 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;
Ява
источник
0

Прежде всего, прочтите все мелким шрифтом. Обратите внимание, что реляционное сопоставление NHibernate (таким образом, я предполагаю, также и Hibernate) имеет забавное соответствие с отображением DB и графа объектов. Например, отношения «один к одному» часто реализуются как отношения «многие к одному».

Во-вторых, прежде чем мы сможем рассказать вам, как вы должны написать свою карту O / R, мы также должны увидеть вашу БД. В частности, могут ли несколько человек владеть одним Навыком? Если это так, то у вас отношения «многие ко многим»; в противном случае - многие к одному.

В-третьих, я предпочитаю не реализовывать отношения «многие ко многим» напрямую, а вместо этого смоделировать «таблицу соединений» в вашей модели предметной области, то есть рассматривать ее как объект, например:

class PersonSkill 
{
    Person person;
    Skill skill;    
}

Тогда вы видите, что у вас есть? У вас есть два отношения "один ко многим". (В этом случае Person может иметь набор PersonSkills, но не иметь набора навыков.) Однако некоторые предпочтут использовать отношение «многие ко многим» (между Person и Skill); это спорно.

В-четвертых, если у вас действительно есть двунаправленные отношения (например, у человека есть не только набор навыков, но и у навыка есть набор людей), NHibernate не применять двунаправленность в вашем BL для вас; он понимает двунаправленность отношений только для целей сохранения.

В-пятых, в NHibernate гораздо проще правильно использовать «многие-к-одному» (и я предполагаю, что Hibernate), чем «один-ко-многим» (отображение коллекций).

Удачи!

jyoungdev
источник