Go: GORM
Теория: Связи между моделями
Когда в приложении появляются несколько сущностей, данные перестают жить в отдельных таблицах. Пользователь пишет посты, посты помечаются тегами, у каждого пользователя есть профиль. В реляционной базе всё это связывается внешними ключами, а в GORM — описывается через поля структур и специальные теги. ORM берёт на себя создание внешних ключей, промежуточных таблиц и правильных JOIN-ов в запросах.
GORM поддерживает четыре базовых типа связей, которые напрямую соответствуют привычным отношениям в базе данных:
- has one — «имеет одну» запись;
- has many — «имеет много»;
- belongs to — «принадлежит»;
- many2many — «многие ко многим».
Связь задаётся комбинацией полей и тегов gorm. ORM анализирует структуру, находит внешние ключи, создаёт нужные столбцы и строит SQL при чтении и записи.
Связь один к одному: has one / belongs to
Связь один к одному удобно рассматривать на примере пользователя и его профиля. У каждого пользователя есть один профиль, у профиля — один владелец.
Здесь поле Profile в структуре User задаёт отношение has one: один пользователь имеет одну запись профиля. Поле UserID в Profile выступает внешним ключом. При миграции GORM создаст таблицу profiles с колонкой user_id и ограничением ссылочной целостности.
Чтобы загрузить пользователя вместе с профилем, используется Preload():
Под капотом GORM выполнит два запроса: сначала выберет строку из users, затем — профиль с profiles.user_id = users.id. Вложенная структура Profile в памяти заполняется автоматически.
Обратная сторона связи — belongs to. Профиль принадлежит пользователю, и иногда удобно явно иметь доступ к владельцу:
Теперь можно загрузить профиль и сразу получить информацию о пользователе через Preload("User").
Связь один ко многим: has many / belongs to
Классический пример связи один ко многим — пользователь и его посты. Один пользователь может написать десятки публикаций, каждый пост принадлежит только одному автору.
Здесь:
- Срез
PostsвUserзадаёт связь has many. - Поле
UserIDвPostхранит идентификатор автора. - Поле
UserвPostпозволяет получить самого.пользователя из поста.
Миграция создаёт две таблицы и внешний ключ:
Добавление пользователя и постов укладывается в несколько строк:
Для выборки автора вместе с его постами снова используется Preload():
GORM выполнит один запрос к users и один к posts с фильтром по user_id.
Связь многие ко многим: many2many
Связь многие ко многим возникает, когда сущности могут ссылаться друг на друга в обе стороны. Например, у поста может быть несколько тегов, и один и тот же тег встречается у разных постов. В реляционной модели для этого заводят промежуточную таблицу — таблицу связей.
В GORM many-to-many описывается с помощью тега gorm:"many2many:<имя_таблицы_связи>":
При миграции GORM создаст три таблицы: posts, tags и post_tags. Последняя содержит пары (post_id, tag_id) и выступает мостом между постами и тегами.
Создание поста с тегами выглядит так:
GORM:
- добавит запись в
posts; - вставит или найдёт теги в
tags; - заполнит таблицу
post_tags— свяжетpost.idс id каждого тега.
Чтобы прочитать пост вместе с тегами, достаточно одного Preload():
Внутри GORM выполнит:
SELECTпо posts;SELECTиз post_tags для всех связей конкретного поста;SELECTиз tags по списку id, полученных на предыдущем шаге.
Добавление нового тега к уже существующему посту делается через Associations() API:
GORM добавит новую строку в post_tags и обновит множество тегов у поста.
Настройка внешних ключей и поведения при удалении
Связи опираются на внешние ключи. По умолчанию GORM ищет поля вида <Имя>ID (UserID, PostID) и связывает их с primary key соответствующей модели. Если имена расходятся с этим шаблоном или требуется особое поведение при удалении и обновлении, параметры задаются явно в тегах.
Простейший пример: пользователь и его заказ. У заказа есть внешний ключ user_id, который ссылается на users.id:
Здесь:
- поле
UserIDхранит id пользователя; - поле
Userзадаёт связь; - constraint описывает поведение внешнего ключа: при изменении id в users значение user_id обновится каскадно, а при удалении пользователя в заказе user_id станет
NULL.
При миграции это превратится в SQL с ограничением:
Если название поля внешнего ключа не следует шаблону, его можно указать явно:
В этом случае GORM понимает, что Profile.AccountID ссылается на Account.ID, даже если имя поля не содержит слова User или другой стандартный префикс.
При нескольких внешних ключах в одной структуре каждый настраивается отдельно:
GORM создаст два отдельных ограничения с разным поведением при удалении, и база данных будет автоматически поддерживать согласованность связей.
Связи в коде играют роль контракта между моделями. Структуры описывают, как сущности связаны между собой, а ORM преобразует это описание в реальные внешние ключи, промежуточные таблицы и JOIN-ы. Пользователи, их посты и теги остаются обычными структурами Go, а вся реляционная логика живёт в миграциях и запросах, которые GORM генерирует под капотом.



