Spring Boot
Теория: Связь «Один ко многим»
В Spring Boot сущности в приложениях существуют не сами по себе — они так или иначе всегда связаны друг с другом. Например, у каждого поста в блоге есть автор, у постов есть комментарии, у комментариев есть авторы и лайки. В этом уроке мы рассмотрим самую распространенную связь между сущностями — «Один ко многим» (OneToMany или O2M). Именно так называют ситуации, когда у одной сущности может быть множество зависимых.
На уровне базы данных связь «Один ко многим» проявляется как внешний ключ. В случае постов, таким внешним ключом будет author_id, указывающий запись в таблице пользователей:
Spring Boot создает и выполняет эти запросы автоматически, если стоит соответствующая опция:
Разновидности «Один ко многим»
Связь «Один ко многим» в Spring Data JPA существует в двух вариантах:
- «Один ко многим» (OneToMany) — устанавливается со стороны сущности, которая представлена в единственном числе. В случае постов и авторов со стороны авторов.
- «Многие к одному» (ManyToOne) — устанавливается со стороны сущности, которая представлена во множественном числе. В случае постов и авторов со стороны постов.
Для создания связи OneToMany нужно как минимум сделать связь ManyToOne для сущности Post. В таком случае мы сможем обращаться к автору поста. Если мы захотим работать с постами одного автора, для этого придется добавить связь OneToMany в сущность User. С практической точки зрения есть смысл сразу устанавливать связь с обеих сторон.
Связь «Многие к одному» (ManyToOne)
Начнем с постов. Для создания связи нам понадобится аннотация @ManyToOne. Ее наличие создает внешний ключ в базе. При этом сама колонка будет называться по имени поля в классе с добавлением _id в конце. В нашем случае имя поля — это author, поэтому поле в базе будет называться author_id:
Почему мы выбрали имя поля author, а не user или что-то подобное? На это есть две причины. Во-первых, имя user не очевидно. Это может быть как создатель поста, так и человек, который опубликовал этот пост. Во-вторых, со временем связей с пользователями даже в рамках одной модели становится больше. Сегодня у нас есть только автор, а завтра может появиться соавтор или модератор.
На практике работа со связью выглядит так же, как и с любым другим полем. Зависимость устанавливается через сеттер и извлекается через геттер:
Но есть и отличия. По умолчанию загрузка связанной сущности происходит в ленивом режиме. Это значит, что при выборке поста из базы автора внутри не будет. Такая стратегия помогает экономить ресурсы. Без нее при большом количестве связей создавалось бы множество ненужных запросов. Реальный запрос в базу происходит тогда, когда мы обращаемся к автору:
Связь «Один ко многим» (OneToMany)
Обратная связь создается с помощью аннотации @OneToMany с параметром mappedBy, значением которого должно быть имя поля в зависимой сущности. В нашей ситуации примере — это author:
С одной стороны, появление двунаправленной связи упрощает выборки, а с другой — создает дополнительную сложность в синхронизации данных в базе и в коде. Представьте, что у нас есть такой код:
Сработает ли этот код? Да, но неправильно. Вновь созданный пост не появится внутри списка постов пользователей. Нам придется добавить его туда вручную:
Такой код допустим. Но нам все еще надо помнить про связь, поэтому есть риск забыть его дописать, что приведет к неочевидным ошибкам. То же самое произойдет и при удалении поста. Ситуацию можно улучшить, если сохранять пост не отдельно, а в рамках пользователя:
В этом коде мы видим два изменения:
- Появились два метода для добавления и удаления поста, которые синхронизируют действия с зависимой сущностью. В одном случае автор добавляется, в другом — удаляется.
- В аннотации появились два параметра:
cascade = CascadeType.ALLотвечает за каскадное применение всех операций к зависимым сущностям. Без него сохранение пользователя не приведет к сохранению поста.orphanRemoval = trueотвечает за удаление зависимых сущностей. Если мы удалим пост из списка постов пользователя, он удалится из базы во время сохранения пользователя.
В этом уроке мы изучили два способа сохранять данные. Чтобы подвести итоги, обсудим их применение на практике:
- Сохранение через зависимую сущность (
Post) стоит использовать всегда, когда есть такая возможность — в большинстве случаев работа с сущностью напрямую проще и эффективнее - Сохранение через базовую сущность (
User) не такое универсальное. Советуем использовать его только тогда, когда мы знаем, что после сохранения зависимой сущности есть код, работающий с базовой сущностью.



