Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 1 из 25 ~15 мин +50 XP

Знакомство с ORM и GORM: что это и зачем

Первый же CRUD-сервис на database/sql расползается в сотни строк ручного Scan: каждая колонка — отдельный аргумент, каждая смена схемы — правка в десятке мест. GORM берёт эту рутину на себя: вы описываете данные обычными структурами Go, а библиотека сама собирает SQL, заполняет поля и ведёт пул соединений. Разберём, что такое ORM, откуда взялся GORM, чем он отличается от sqlc и ent — и когда без него действительно лучше.

Содержание урока · 10 разделов

Возьмём типовую задачу: бэкенд с таблицей задач — создать, показать список, отредактировать, удалить. На чистом database/sql каждая операция расписывается руками: написать SQL, открыть строки, перечислить колонки в Scan ровно в том порядке, в каком они идут в SELECT, закрыть rows и не забыть проверить ошибку после цикла. Работает — но каждый новый столбец требует правки сразу в трёх местах: в SQL, в Scan и в структуре. Именно на этом стыке и появляются самые нудные баги: колонку добавили в запрос, а в Scan забыли, и приложение падает уже в рантайме. ORM — Object-Relational Mapping, «объектно-реляционное отображение» — выносит эту рутину в библиотеку: вы объявляете структуру, а маппинг «колонка-поле», сборка запросов и сканирование делает она. В Go самой популярной такой библиотекой больше десяти лет остаётся GORM, и этот курс — про неё.

Как это выглядит на чистом database/sql

GO
func listTodos(db *sql.DB, userID int64) ([]Todo, error) {
        rows, err := db.Query(
                "SELECT id, title, done FROM todos WHERE user_id = $1", userID)
        if err != nil {
                return nil, err
        }
        defer rows.Close()

        var todos []Todo
        for rows.Next() {
                var t Todo
                if err := rows.Scan(&t.ID, &t.Title, &t.Done); err != nil {
                        return nil, err
                }
                todos = append(todos, t)
        }
        return todos, rows.Err()
}

Пятнадцать строк на одну выборку — и ни одна из них не содержит бизнес-логики. Scan привязан к порядку колонок: поменяете их местами в SELECT — данные молча съедут в чужие поля. rows.Err() после цикла забывается чаще всего, и обрыв соединения маскируется под пустой список. Именно эту лестницу GORM сворачивает в одну строку.

То же самое на GORM

GO
func listTodos(gdb *gorm.DB, userID int64) ([]Todo, error) {
        var todos []Todo
        err := gdb.Where("user_id = ?", userID).Find(&todos).Error
        return todos, err
}

Find сам напишет SELECT, пройдёт по строкам и разложит колонки по полям структуры — по именам, а не по порядку. Where принимает плейсхолдер ?, в который параметр уходит через драйвер, без конкатенации строк: SQL-инъекцию так собрать не выйдет. Ошибка возвращается полем Error у результата цепочки.

Что такое ORM: мост между строками и структурами#

Реляционная база живёт таблицами со строками и колонками, Go — структурами и срезами. Между этими двумя мирами всегда есть трение, его называют impedance mismatch: типы не совпадают, NULL некуда класть, связи надо собирать вручную из внешних ключей. ORM — это слой, который берёт трение на себя: знает, что структура User соответствует таблице users, что поле Email — это колонка email, и что при сохранении объекта нужно собрать INSERT, а при чтении — просканировать строки обратно в структуры.

Важно понимать, что GORM не отменяет SQL — он его генерирует. Каждая цепочка методов вроде Where(...).Order(...).Limit(...) превращается в конкретный запрос, который можно увидеть в логах и проверить глазами. Поэтому ORM — это не «программирование без SQL», а другой способ его писать: вместо текста запроса вы описываете намерение, а текст собирает библиотека. Если SQL вы знаете хорошо, GORM становится предсказуемым: вы всегда догадываетесь, какой запрос получится. Если не знаете — ORM превращается в генератор неэффективных запросов, и это главный скрытый риск всей категории инструментов.

История GORM: v1, v2 и путь модуля gorm.io/gorm#

GORM написал Jinzhu Zhang, первая версия появилась в 2013 году и распространялась как github.com/jinzhu/gorm. Библиотека быстро стала дефолтным ответом на вопрос «как с базой в Go» — не в последнюю очередь потому, что в стандартной библиотеке долгое время не было ничего, кроме низкоуровневого database/sql. В 2020 году автор выпустил полностью переписанную вторую версию: новый модуль gorm.io/gorm, собственный парсер схем, диалекты как подключаемые пакеты, цепочечный API с клонированием запросов. Весь этот курс — про вторую версию; если в старых статьях видите импорт jinzhu/gorm, перед вами первый GORM, и его синтаксис местами отличается.

Сегодня GORM — самая звёздная ORM в экосистеме Go (больше тридцати тысяч звёзд на GitHub на момент написания), с документацией на gorm.io и большим количеством готовых плагинов: драйверы для разных СУБД, пагинация, трассировка, шифрование. Обратная сторона популярности — почти на любой вопрос по GORM ответ уже лежит в Stack Overflow, а сообщение об ошибке легко гуглится.

Что GORM берёт на себя#

  • CRUD через цепочечный API: Create, First/Find, Where, Update, Delete — и сборка SQL под конкретную СУБД
  • Миграции схемы: AutoMigrate создаёт и обновляет таблицы по структурам (урок 04)
  • Ассоциации: belongs to, has one, has many, many-to-many и жадная загрузка Preload (уроки 11–14)
  • Транзакции: обёртка db.Transaction с откатом по ошибке (урок 15)
  • Хуки жизненного цикла: BeforeCreate, AfterUpdate и другие (урок 16)
  • Мягкое удаление: колонка deleted_at вместо физического DELETE (урок 09)
  • Лазейка для сырого SQL: Raw и Exec, когда генератора не хватает (урок 10)
  • Пул соединений и контекст: обёртка над стандартным database/sql, поддержка WithContext

Четыре способа работать с базой из Go#

ПодходЧто пишете выСильная сторонаСлабая сторона
database/sqlSQL и Scan вручнуюполный контроль, ноль магиимногословно, всё руками
GORMструктуры + цепочки методовскорость разработки, ассоциации, миграциинеэффективный SQL при незнании базы
sqlcчистый SQL-файлтипобезопасный код из ваших запросовсложные динамические фильтры
entсхема как кодстрогая типизация графа связейвысокий порог входа

Выбор между подходами — это выбор между стоимостью старта и стоимостью сопровождения. database/sql бесплатен в зависимостях, но платит многословием на каждом запросе. sqlc генерирует из ваших SQL-запросов типизированный Go-код — за деталями идите в курс sqlc. ent описывает схему данных как код с сильной типизацией графа связей — у нас есть отдельный курс ent. GORM занимает середину: минимум церемоний на старте и достаточно места, чтобы выстрелить себе в ногу сложным запросом. Для CRUD-приложений — админок, каталогов, бэкендов с формами — эта середина почти всегда выгодна. Для аналитических систем с тяжёлыми отчётами расклад другой, и об этом честно поговорим ниже.

Первый взгляд на код с GORM: Create и First#

Чтобы почувствовать библиотеку, посмотрим на минимальный рабочий фрагмент: структура, сохранение записи и чтение по первичному ключу. Пока не вникайте в gorm.Open и типы — им посвящён следующий урок; сейчас важно увидеть общий ритм работы.

Код

GO
type User struct {
        gorm.Model
        Name  string
        Email string `gorm:"uniqueIndex"`
}

// сохранить
user := User{Name: "Анна", Email: "anna@example.com"}
result := db.Create(&user)
// INSERT INTO "users" ("name","email","created_at",...)
// VALUES ('Анна','anna@example.com',...) RETURNING "id"

// прочитать по первичному ключу
var found User
err := db.First(&found, user.ID).Error
// SELECT * FROM "users" WHERE "users"."id" = 1 ORDER BY "users"."id" LIMIT 1

Встроенный gorm.Model добавляет четыре служебных поля: ID, CreatedAt, UpdatedAt и DeletedAt — о них подробно в уроке 03. Create сохраняет структуру одной строкой и возвращает сгенерированный ID прямо в объект: в PostgreSQL это возможно благодаря RETURNING. First читает запись по первичному ключу, сортируя по id и ограничивая выборку одной строкой; если записи нет, вернётся gorm.ErrRecordNotFound, а не паника.

Когда GORM вредит: моё мнение#

GORM отлично закрывает CRUD, но есть класс задач, где я его сознательно не применяю: аналитические отчёты, оконные функции, сравнения периодов, агрегаты по десяткам тысяч строк. Запросы такого сорта пишутся на SQL быстро и понятно, а на цепочках методов — плохо: получается читаемый только автору конструктор, который ещё и генерирует неоптимальный план. Моё правило: если больше половины запросов модуля — агрегаты и отчёты, этот модуль лучше держать на сыром SQL (GORM умеет Raw/Exec, урок 10, либо sqlc рядом). Если же это типовой сервис с формами и списками — GORM окупается с первого дня.

Второй спорный пункт — теги вместо явной схемы. Схема данных живёт в комментариях к полям структуры, и «единственный источник правды» размазывается между кодом и базой. В команде с DBA это источник споров; в solo-проекте или стартапе — честная экономия времени. Выбирайте по составу команды, а не по хайпу.

Проверка: основы GORM

Какой путь модуля у GORM второй версии?

Проверка: как работает маппинг

Find сканирует строки результата в структуры. Как колонки попадают в поля?

Заполните пропуски: выборка на GORM

Восстановите цепочку запроса: фильтр по email и чтение одной записи по первичному ключу.

var users []User err := gdb.("email LIKE ?", "%@example.com").(&users) var anna User err = gdb.(&anna, 7) // по первичному ключу

Соберите минимальную программу с GORM

Расставьте строки в правильном порядке: от объявления пакета до сохранения записи.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Карточки: словарь GORM

Термин

ORM

нажмите, чтобы перевернуть
Определение

Object-Relational Mapping — отображение структур Go на таблицы БД: маппинг колонок, сборка SQL, сканирование строк обратно в объекты.

1 / 6

Что дальше по курсу#

Во втором уроке подключим базу данных: gorm.Open, DSN для PostgreSQL и SQLite, пул соединений. В третьем — модели и соглашения: как из структуры получается таблица, зачем нужен gorm.Model и что умеют теги. Дальше — миграции, CRUD, ассоциации, транзакции и итоговый проект: слой данных блога. Если захочется сверяться с официальной документацией, всё то же самое изложено на gorm.io — наш курс идёт параллельно ей, но по-русски и с разбором граблей.

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.

Вывод

Запустите код, чтобы увидеть вывод.

Коротко: что запомнить

  • ORM — слой между структурами Go и таблицами: маппинг полей, сборка SQL, сканирование строк.
  • GORM не отменяет SQL — он его генерирует; понимать итоговый запрос обязан сам разработчик.
  • Актуальная версия — v2 с модулем gorm.io/gorm; jinzhu/gorm — устаревшая v1.
  • GORM закрывает CRUD, ассоциации, транзакции, хуки, миграции и мягкое удаление.
  • Для отчётов и оконных функций я беру сырой SQL или sqlc — цепочки методов там мешают.
  • Песочница не исполняет GORM: нужен локальный Go и SQLite/Postgres для практики.

Домашнее задание

Соберите локальный стартовый модуль: go mod init demo, установите gorm.io/gorm и gorm.io/driver/sqlite (или postgres), объявите структуру User с gorm.Model, создайте через AutoMigrate таблицу и сохраните запись через Create. Затем прочитайте её по ID через First и выведите имя в консоль. Если знакомы с sqlite3 CLI — загляните в файл базы и найдите свои колонки.

Личные заметкиПусто
Хранятся только в вашем браузере (GORM · Знакомство с ORM и GORM: что это и зачем)

Вопросы и ответы по уроку

Что такое GORM простыми словами?

Библиотека, которая по вашим структурам Go сама пишет SQL: сохраняет объекты строками таблиц, читает их обратно и ведёт соединения с базой. Вы описываете данные, GORM — запросы.

Нужен ли GORM, если я хорошо знаю SQL?

Да, но с оговоркой: GORM экономит время на CRUD и миграциях, а сложные отчёты вы всё равно пишете сырым SQL через Raw или рядом с sqlc. Знание SQL делает работу с ORM предсказуемой.

Чем GORM отличается от sqlc и ent?

GORM — классический ORM с цепочками методов и автомиграциями. sqlc генерирует код из ваших SQL-файлов, ent — строит строго типизированную схему-граф. GORM быстрее всех в старт, sqlc строже в контроле запросов.

Термины урока

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

Сравните с альтернативами

Практический вопрос после первых уроков: а почему именно эта библиотека? Мы разобрали конкурентов по одним критериям, с кодом одной задачи рядом и персональным подбором под ваши приоритеты.

Следующая ступень учебного пути

sqlc25 уроков~12 чпрактика25 уроков уже доступны

sqlc: типобезопасный код из SQL-запросов

Курс sqlc: настройка, запросы, параметры, NULL-типы, транзакции, джойны, пагинация, CI и сравнение с ORM. 25 уроков с практикой.

  • Настраивать sqlc.yaml для Postgres/MySQL/SQLite
  • Писать аннотированные запросы с типизированными параметрами
  • Работать с NULL, массивами и JSON-колонками
  • Организовывать транзакции через WithTx