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

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

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


person Ian Dallas    schedule 24.06.2010    source источник
comment
Похоже, все отвечают только «один ко многим» против «многие ко многим». Посмотрите на мой ответ «Один ко многим» против «Многие к одному».   -  person Alexander Suraphel    schedule 12.11.2013


Ответы (8)


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

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

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

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

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

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

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

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

person HDave    schedule 24.06.2010
comment
Чтобы прояснить, любые отношения могут быть однонаправленными или двунаправленными в вашем BL или в вашем отображении O / R (независимо друг от друга, даже!). - person jyoungdev; 25.06.2010
comment
Пример ЛЮБВИ только прояснил это. ManyToMany - это мой тип отображения. - person Abdullah Khan; 21.01.2016
comment
Супер. Это так хорошо объясняет (и в контексте примера OP) - person Anupam; 10.03.2017
comment
Не отвечает на вопрос должным образом. вы упускаете из виду многие из них. хотя один ко многим и многие к одному - это вопрос восприятия, в этом ответе об этом не упоминается. - person Manzur Alahi; 16.04.2020

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

Разница между One-to-many, Many-to-one и Many-to-Many:

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

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

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

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

  • В Many-to-Many члены каждой партии могут содержать ссылку на произвольное количество членов другой стороны. Для этого используется таблица поиска. Примером этого являются отношения между врачами и пациентами. У врача может быть много пациентов и наоборот.
person Alexander Suraphel    schedule 12.11.2013
comment
это должен быть принятый ответ, в большинстве других ответов вопрос отсутствует. - person arg20; 15.03.2014
comment
Первый пример неверен. Если у вас есть человек, а у человека есть @OneToMany с навыками, эта таблица preson_skills будет иметь уникальное ограничение на skill_id. Таким образом, один навык может быть назначен только одному человеку. И вы не можете извлечь s.persons, так как есть только s.person - person Иван Николайчук; 04.11.2016
comment
Фактически One-to-many отношение, как вы описываете, это Many-to-many отношение, потому что person имеет ссылку на многие skills, но skill не сохраняет ссылку на конкретного человека, и многие persons могут иметь ссылку на то же skill. И ваши Many-to-one отношения на самом деле One-to-many, потому что каждый навык относится только к одному person, поскольку у ребенка только одна мать. - person mixel; 06.02.2017
comment
@mixel, ваш комментарий заставил меня переписать большую часть своего ответа. Пожалуйста, проверьте это еще раз! Спасибо - person Alexander Suraphel; 01.03.2017
comment
Вы различаете «Один ко многим» и «Многие к одному» по значению Одной стороны. В обоих примерах пользователь является наиболее значимой сущностью. Поэтому, когда он находится на Одной (пользователи и навыки, skills имеет person_id), вы называете это Один-ко-многим, но когда это одна сторона Многих (пользователи и адреса, users имеет address_id), тогда вы называете это Многие-к-одному. Но конструктивно оба случая идентичны и называются One-to-many. - person mixel; 01.03.2017
comment
Также однонаправленный пример "один ко многим", когда Person имеют List<Skill> skills, на самом деле является "многие ко многим", потому что у человека может быть много навыков, а навыки могут быть во многих List<Skill> skills списках. Думаю, вы хотели написать В однонаправленном Skill классе будет Person person. - person mixel; 01.03.2017
comment
@mixel Теперь я понимаю вашу точку зрения, но я не думаю, что тот факт, что List<Skill> skills существует в Person, автоматически делает связь @Many-to-many, потому что в сгенерированной схеме может быть unique ограничение на person_id, делающее навык принадлежащим одному человеку. Имеет смысл? - person Alexander Suraphel; 02.03.2017
comment
Да, но вы не упомянули, что в ответе и по-прежнему в Java это будет «Многие ко многим», хотя в сгенерированной схеме SQL оно будет иметь уникальное ограничение, которое может привести к ошибкам времени выполнения. Обсуждая конкретный случай, мы должны обратить внимание на несколько аспектов - структуру, семантику и реализации (на разных уровнях приложения). Таким образом, в обоих примерах структурно они являются «Один ко многим». Семантически они являются «один ко многим» и «многие к одному». - person mixel; 03.03.2017
comment
Ваш ответ отличается от других, потому что вы заметили семантическую разницу, но когда вы говорите об отношениях, они обычно обсуждаются в структурном аспекте. Реализация на всех уровнях приложений также должна быть «один ко многим», но некоторые разработчики могут реализовать ее как «один ко многим» в базе данных и «многие ко многим» в Java (как вы предлагаете), что может привести к ошибкам времени выполнения, как я уже сказал. выше. - person mixel; 03.03.2017
comment
Очень запутанные примеры! Примеры в других ответах лучше. Короче говоря, ManyToOne == OneToMany теоретически, но поскольку это отношение означает, что существует внешний ключ на многих сторонах, в ORM его легче определить как ManyToOne (однонаправленный). Чтобы сделать OneToMany, это означает, что нужно удерживать множество, поэтому он должен быть двунаправленным (определенным с обеих сторон) - person eyalyoli; 27.06.2021
comment
@eyalyoli Буду признателен, если вы предложите лучшие примеры ... В конце концов, мы все здесь, чтобы учиться! - person Alexander Suraphel; 01.07.2021
comment
@AlexanderSuraphel Пример сообщений и комментариев Влада Михалчи является классическим для отношений "один ко многим". 1 сообщение имеет много комментариев. Чтобы реализовать это, SQL использует внешний ключ в комментариях, который указывает на сообщение, которому он принадлежит, это отношение «многие к одному». Но часто вам нужно загрузить сообщение x с его комментариями, поэтому ORM имеет это виртуальное отношение «один ко многим», чтобы упростить его (это просто обратное направление). В качестве примера "многие ко многим" возьмем студентов и классы, у каждого студента есть много классов, в то время как в каждом классе много студентов, в SQL у вас будет дополнительная таблица учеников и классов, чтобы сделать это отношение - person eyalyoli; 05.07.2021

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

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

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

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

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

person jhegedus    schedule 02.06.2013
comment
Мне действительно нравится, как он связывает граф объектов с таблицами в обоих направлениях. - person Dmitry Minkovsky; 03.08.2016
comment
Смотри, ботаники, вот как выглядит ПРОГРАММИРОВАНИЕ РУКОВОДСТВО: D - person Mehraj Malik; 17.04.2017
comment
Я вижу, что вы здесь сделали. - person Nick Gallimore; 16.04.2019

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

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

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

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

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

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

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

Один к одному

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

 Один к одному

В системе реляционной базы данных взаимно-однозначное отношение таблиц связывает две таблицы на основе 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.

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

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

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

В системе реляционной базы данных отношение таблиц «многие ко многим» связывает две родительские таблицы через дочернюю таблицу, которая содержит два 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 со столбцом tag идентификатора таблицы 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 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. Ассоциация tags в сущности Post определяет только каскадные типы PERSIST и MERGE. Переход состояния объекта REMOVE не имеет никакого смысла для @ManyToMany JPA-ассоциации, поскольку он может вызвать удаление цепочки, которое в конечном итоге приведет к стиранию обеих сторон ассоциации.
  2. Служебные методы добавления / удаления являются обязательными, если вы используете двунаправленные ассоциации, чтобы вы могли убедиться, что обе стороны ассоциации синхронизированы.
  3. Сущность Post использует идентификатор объекта для равенства, поскольку у нее нет уникального бизнес-ключа. Вы можете использовать идентификатор объекта для равенства, если убедитесь, что он остается согласованным при всех переходах состояния объекта.
  4. Сущность Tag имеет уникальный бизнес-ключ, помеченный аннотацией @NaturalId для Hibernate. В этом случае уникальный бизнес-ключ является лучшим кандидатом для проверки равенства .
  5. Атрибут mappedBy ассоциации posts в сущности Tag отмечает, что в этой двунаправленной связи сущность Post владеет ассоциацией. Это необходимо, поскольку только одна сторона может владеть отношением, и изменения распространяются в базу данных только с этой конкретной стороны.
  6. Предпочтительно использовать Set, поскольку использование List с @ManyToMany менее эффективно.
person Vlad Mihalcea    schedule 16.05.2020

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

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

*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. 
person alejandrobog    schedule 24.06.2010
comment
this occurs when the maximum of one multiplicity is one and the other is greater than one лолвут? - person serg; 25.06.2010

Я бы так объяснил:

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;
person Java    schedule 06.10.2019

это, вероятно, потребовало бы отношения "многие ко многим" следующим образом



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, но он будет работать и без ...

person msshapira    schedule 24.06.2010

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

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

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

class PersonSkill 
{
    Person person;
    Skill skill;    
}

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

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

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

Удачи!

person jyoungdev    schedule 24.06.2010