---
title: "Знакомство с ORM и GORM: что это и зачем"
description: "Что такое GORM и ORM для Go: история библиотеки, сравнение с database/sql, sqlc и ent, когда ORM вредит, и первый код Create/First с разбором строк."
course: "GORM: ORM-библиотека для Go"
url: "https://golangmaster.ru/courses/gorm/lessons/01-znakomstvo/"
published: "2026-09-19"
updated: "2026-09-21"
minutes: 15
---

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

*Курс «GORM: ORM-библиотека для Go», урок 1 из 25. ~15 минут. Обновлено 21.09.2026. Источник: https://golangmaster.ru/courses/gorm/lessons/01-znakomstvo/*

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

Возьмём типовую задачу: бэкенд с таблицей задач — создать, показать список, отредактировать, удалить. На чистом 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/sql | SQL и Scan вручную | полный контроль, ноль магии | многословно, всё руками |
| GORM | структуры + цепочки методов | скорость разработки, ассоциации, миграции | неэффективный SQL при незнании базы |
| sqlc | чистый SQL-файл | типобезопасный код из ваших запросов | сложные динамические фильтры |
| ent | схема как код | строгая типизация графа связей | высокий порог входа |

Выбор между подходами — это выбор между стоимостью старта и стоимостью сопровождения. database/sql бесплатен в зависимостях, но платит многословием на каждом запросе. sqlc генерирует из ваших SQL-запросов типизированный Go-код — за деталями идите в [курс sqlc](/courses/sqlc/). ent описывает схему данных как код с сильной типизацией графа связей — у нас есть отдельный [курс ent](/courses/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.** Браузерный интерпретатор этого сайта исполняет только чистый Go без сети и файловой системы, поэтому код с GORM в уроках не запускается в песочнице. Чтобы гонять примеры руками, нужен локальный Go и база: проще всего начать с SQLite (файл вместо сервера) или поднять PostgreSQL в Docker. Учтите: gorm.io/driver/sqlite требует CGO (нужен компилятор C), а если хочется чистого Go — есть драйвер github.com/glebarez/sqlite. Драйвер PostgreSQL — gorm.io/driver/postgres — работает без CGO.

> **Что нужно знать перед курсом.** Понадобятся структуры, указатели, методы и ошибки — всё это есть в [базовом курсе](/courses/go-basics/). Интерактивы урока (тесты, «заполни пропуск», «собери программу») дают XP и работают прямо в браузере, так что базу проверите даже без локальной базы данных.

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

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

- github.com/jinzhu/gorm
- ✅ gorm.io/gorm
- github.com/go-gorm/gorm.v2
- gopkg.in/gorm.v2

v2 живёт в модуле gorm.io/gorm с 2020 года. github.com/jinzhu/gorm — первая версия, которая больше не поддерживается. Репозиторий на GitHub называется go-gorm/gorm, но путь импорта в go.mod — именно gorm.io/gorm.

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

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

- Строго по порядку, как в Scan у database/sql
- ✅ По именам: колонка email ложится в поле Email по правилу snake_case-в-CamelCase
- Только по тегам json на полях структуры
- По порядку объявления полей, начиная со второго

GORM сопоставляет колонки и поля по именам, приводя их к единому виду: full_name и FullName — одна колонка. Поэтому перестановка колонок в SELECT не ломает сканирование — в отличие от database/sql, где порядок критичен.

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

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

```go
var users []User
err := gdb.___("email LIKE ?", "%@example.com").___(&users)

var anna User
err = gdb.___(&anna, 7) // по первичному ключу
```

Подсказка: Условие добавляет Where, список выбирает Find, запись по ID читает First.

Правильные ответы в пропусках: `Where`, `Find`, `First`

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

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

Правильный порядок строк:

1. `package main`
2. `import (`
3. `  "gorm.io/driver/sqlite"`
4. `  "gorm.io/gorm"`
5. `)`
6. `func main() {`
7. `  db, err := gorm.Open(sqlite.Open("app.db"), &gorm.Config{})`
8. `  if err != nil {`
9. `    panic(err)`
10. `  }`
11. `  db.Create(&User{Name: "Анна"})`
12. `}`

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

- **ORM** — Object-Relational Mapping — отображение структур Go на таблицы БД: маппинг колонок, сборка SQL, сканирование строк обратно в объекты.
- **GORM** — Самая популярная ORM для Go (с 2013 года, v2 — с 2020). Модуль v2 — gorm.io/gorm; автор — Jinzhu Zhang.
- **Dialector** — Плагин-драйвер, через который GORM общается с конкретной СУБД: postgres, mysql, sqlite, sqlserver. Подключается отдельным пакетом gorm.io/driver/*.
- **gorm.Model** — Встраиваемая структура с полями ID, CreatedAt, UpdatedAt, DeletedAt: первичный ключ, автотаймстампы и мягкое удаление.
- **gorm.ErrRecordNotFound** — Ошибка, которую возвращают First/Take/Last, когда под условия не попало ни одной строки. Проверяется через errors.Is.
- **database/sql** — Низкоуровневый пакет стандартной библиотеки: строки подключения, пул, Query/Scan. GORM построен поверх него и отдаёт доступ через db.DB().

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

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

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

- 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 простыми словами?

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

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

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

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

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