Go: GORM
Теория: Загрузка связанных данных
Как только в приложении появляются связи между моделями, вопрос «как загрузить данные» усложняется. Уже редко хватает «просто выбрать пользователей» — почти всегда нужно сразу получить их посты, теги, профиль, заказы. В чистом SQL это быстро превращается в набор JOIN’ов и ручное разруливание дубликатов строк. В GORM та же задача решается двумя приёмами: Preload() и Joins(), плюс управлением тем, когда именно подгружать связи.
Preload и Joins
Preload() — основной способ подгрузки связей. Он делает дополнительные SELECT-запросы, связывает данные по внешним ключам и аккуратно раскладывает их в структуры. Joins() — более низкоуровневый инструмент: он строит SQL с JOIN прямо на стороне базы и возвращает плоский результат.
Пример: есть пользователь и его посты.
Жадная подгрузка постов через Preload():
GORM под капотом сделает два запроса:
Он соберёт результат в памяти: каждому пользователю сопоставит его посты по user_id.
К тому же Preload() умеет фильтровать связи:
SQL для связей будет таким:
Joins() полезен, когда фильтровать нужно не связи, а основную сущность по данным в связанных таблицах.
SQL получится примерно таким:
Частый приём: использовать Joins() для фильтрации, а Preload() — для аккуратной подгрузки связей:
Здесь Joins() отберёт пользователей, а Preload("Posts") подгрузит для них все посты (не только отфильтрованные).
Отложенная и жадная загрузка
По умолчанию GORM не подгружает связи «автоматом». Это и есть отложенная (lazy) загрузка: сначала в память попадает только основная модель, а связи загружаются отдельно, когда это явно нужно.
Отложенная загрузка:
Сначала будет:
а при Association("Posts").Find — отдельный запрос:
Жадная (eager) загрузка делается через Preload(): связи приходят сразу, в рамках одного вызова GORM.
Теперь GORM выполнит два запроса подряд (к users и posts), но для кода это один вызов.
Вложенные связи тоже можно подгружать жадно:
Под капотом GORM:
- выберет пользователя,
- выберет его посты,
- выберет комментарии к этим постам,
- соберёт всё дерево в памяти.
На практике удобно комбинировать подходы: список сущностей получать с жадной подгрузкой (например, пользователей и их посты), а тяжёлые связи (комментарии, историю изменений) подгружать отложенно только там, где они действительно нужны.
Ограничения и условия при загрузке связей
Часто нужно не просто «подгрузить все связи», а сделать это с фильтром, сортировкой или лимитом. Preload() поддерживает условия и принимает вторым параметром либо SQL-строку с аргументами, либо функцию, которая настраивает вложенный *gorm.DB.
Простой фильтр по флагу:
SQL для связей:
Сортировка и ограничение количества подгружаемых записей через функцию:
Для каждой связи GORM выполнит запрос вида:
Разным связям можно задать разные ограничения:
Условия работают и для вложенных связей:
А если нужно отфильтровать основную сущность по связям и в то же время аккуратно подгрузить связи — соединяем Joins() и Preload():
Joins() ограничит список пользователей, Preload() сделает второй запрос и подгрузит к ним все опубликованные посты.
Загрузка связанных данных в GORM — это управляемый процесс. Preload() отвечает за жадную подгрузку связей, Association — за отложенную загрузку по требованию, Joins() — за фильтрацию основной сущности по связям. Условия и ограничения внутри Preload() позволяют не перетаскивать из базы лишнее. Вся магия связей остаётся внутри ORM, а код работает с обычными структурами Go.



