Go: GORM
Теория: CRUD-операции
После того как схема базы синхронизирована с моделями, начинается самое главное — повседневная работа с данными. Любое приложение снова и снова делает одни и те же шаги: добавляет новые записи, читает существующие, меняет поля и удаляет устаревшую информацию. Эти четыре действия объединяют под общим названием CRUD: Create(), Read(), Update(), Delete(). GORM предлагает для каждого шага свои методы и берёт на себя превращение вызовов на Go в конкретные SQL-запросы. Код остаётся на уровне структур и логики, а таблицы и операторы остаются за кулисами.
GORM ведёт себя как переводчик между миром структур и миром SQL. Когда программа вызывает db.Create, библиотека разбирает структуру, читает теги и значения полей, строит INSERT, подставляет параметры и отправляет запрос в базу. Методы First(), Find() и Where() превращаются в SELECT с нужными условиями и ограничениями. Updates() и Update() становятся UPDATE, Delete() — либо DELETE, либо UPDATE с заполнением поля DeletedAt при мягком удалении. В ответ GORM получает данные и аккуратно раскладывает их обратно по полям структур Go, сохраняя типы и значения.
Создание записей: Create
Создание данных начинается с формирования значения структуры. Программа описывает новую сущность, а GORM берёт на себя запись в таблицу. В SQL для этого используют INSERT INTO, но в GORM достаточно одного вызова Create(), который принимает указатель на структуру или срез структур. Библиотека сама определяет, какие поля участвуют во вставке, какие заполняются значениями по умолчанию, и получает автоинкрементный идентификатор, если он есть.
Простейший пример показывает этот путь от структуры до строки в таблице:
Под капотом GORM превращает этот вызов в команду INSERT с перечислением колонок name, email, created_at, updated_at и значениями, подставленными из структуры. Если в модели есть служебные поля CreatedAt и UpdatedAt, библиотека сама устанавливает в них текущее время. После выполнения запроса драйвер возвращает сгенерированный идентификатор, и GORM записывает его обратно в поле ID.
При массовой вставке срез структур превращается в один батчевый запрос. Программа создаёт набор значений, а GORM формирует INSERT с несколькими строками:
Такой подход снижает количество запросов и ускоряет добавление большого количества данных. При необходимости программа может ограничить список полей, которые попадут в INSERT, через Select() или исключить отдельные колонки через Omit(). GORM сформирует SQL под эту выборку и проигнорирует остальные значения структуры.
Чтение данных: First, Find, Where
После вставки данных встаёт задача читать их в разных разрезах. Иногда нужно получить одну запись по первичному ключу, иногда — список всех сущностей, иногда — выборку по сложному условию. В чистом SQL это серия SELECT-запросов. В GORM основной набор методов для чтения состоит из First(), Find() и Where(). Эти методы комбинируются между собой и позволяют строить выборки шаг за шагом.
Метод First() ориентирован на получение одной записи. Если передать только структуру, GORM выберет первую строку по возрастанию первичного ключа:
Если программе известен первичный ключ, его можно передать вторым аргументом. В этом случае GORM сформирует запрос с WHERE id = ? и LIMIT 1:
Для выборки нескольких строк используется Find(). Этот метод заполняет срез структур и может комбинироваться с фильтрами и сортировкой:
Фильтрация добавляется методом Where(). Он может принимать SQL-условия с подстановками, структуру или карту. Пример выборки по возрасту и почте показывает работу с текстовым условием:
GORM превращает цепочку Where() в одно выражение WHERE с объединёнными условиями. Для выборки конкретных колонок используется Select(). Тогда в структуру будут загружены только запрошенные поля, а остальные останутся нулевыми значениями:
Во всех этих случаях GORM формирует объект Statement с именем таблицы, списком полей, фильтрами, сортировкой и ограничениями. Затем ORM строит SQL под конкретную СУБД, выполняет запрос и сканирует строки результата в структуры Go, автоматически приводя типы.
Обновление данных: Save, Update, Updates
Когда сущность уже существует в базе, её состояние постепенно меняется. Имя пользователя обновляется, почта переходит на новый домен, количество заказов растёт. В SQL эти изменения описываются командой UPDATE. В GORM для обновления служат два основных подхода: Save() для сохранения всей структуры и Update() / Updates() для частичных изменений.
Метод Save() воспринимает структуру как снимок всей записи. Если у объекта заполнен первичный ключ, GORM считает, что строка уже существует, и выполняет UPDATE. Если ключ пустой, ORM воспринимает структуру как новую запись и делает INSERT. Такой подход делает Save() универсальным, но иногда слишком широким: он обновляет все поля, включая нулевые.
Метод Updates() ориентирован на частичные изменения. Он принимает либо структуру, либо карту и модифицирует только указанные поля. Остальные колонки остаются без изменений. При передаче структуры нулевые значения пропускаются, а при передаче map обновляются все перечисленные поля, даже если они равны нулю.
Когда важно записать и нулевые значения, программа передаёт карту:
Во всех этих сценариях GORM строит UPDATE для таблицы users, подставляет в SET только перечисленные колонки и добавляет условие WHERE по первичному ключу. Если в модели есть поле UpdatedAt, ORM автоматически обновляет его текущим временем, чтобы отражать момент последнего изменения записи.
Удаление данных: Delete и мягкое удаление
Удаление закрывает цикл CRUD. В какой-то момент записи перестают быть нужными и должны либо исчезнуть из системы, либо перейти в разряд архивных. В SQL для этого существует DELETE. В GORM удаление может быть двух видов: физическое удаление строки и мягкое удаление, когда запись остаётся в таблице, но помечается как скрытая с помощью DeletedAt.
Если модель не содержит DeletedAt, Delete() превращается в прямой DELETE:
Когда структура включает поле DeletedAt типа gorm.DeletedAt, GORM пер��ключается на мягкое удаление. Вместо уничтожения строки ORM устанавливает в DeletedAt текущее время, а при обычных выборках такие записи автоматически игнорируются:
При последующих вызовах Find() и First() GORM добавляет в запрос условие deleted_at IS NULL и исключает помеченные строки. Чтобы увидеть все записи, включая мягко удалённые, программа использует Unscoped(). Тот же метод применяется для окончательного удаления:
Такой механизм позволяет сначала безопасно скрывать данные, а затем, при необходимости, выполнять физическую очистку базы в отдельном процессе или по отдельному сценарию.
Работа с первичным ключом
Первичный ключ остаётся центральной точкой для всех CRUD-операций. GORM автоматически использует поле ID, если оно присутствует в модели, и строит по нему условия для First(), Save(), Updates() и Delete(). При создании записи ORM получает значение ключа от базы и записывает его в структуру. При выборке по ключу GORM принимает значение вторым аргументом и превращает его в условие WHERE.
Простой сценарий показывает полный цикл работы с первичным ключом:
Если модель использует другой тип ключа или составной ключ, GORM подстраивает под него условия и не привязывается жестко к полю ID. Главное, чтобы структура однозначно описывала, по каким полям поиск и обновление должны находить нужную строку.
Подведем итоги
CRUD-операции составляют основной рабочий цикл GORM. Создание через Create(), чтение через First(), Find() и Where(), обновление через Save() и Updates(), удаление через Delete() и Unscoped() позволяют работать с базой данных в терминах структур Go. Под капотом библиотека строит SQL-запросы, управляет первичными ключами, заполняет служебные поля и обрабатывает ошибки. Логгер делает этот процесс прозрачным: при необходимости можно увидеть каждый запрос и его параметры. Понимание того, как GORM переводит CRUD в SQL, помогает писать предсказуемый код и избегать неожиданных нагрузок на базу.



