Go: GORM

Теория: Определение моделей

После подключения к базе данных GORM должен «увидеть», с какими сущностями он работает. Для этого в коде описываются модели — обычные структуры Go, которые связываются с таблицами в базе. Каждая структура играет роль чертежа, по которому GORM строит схему. Имя типа превращается в имя таблицы, поле структуры становится столбцом, а типы Go отображаются в типы СУБД. Такой подход позволяет описывать данные на языке предметной области, а преобразование в SQL доверять ORM.

GORM строит таблицы и запросы, опираясь на описание структур. По умолчанию имя структуры переводится в нижний регистр и во множественное число: тип User превращается в таблицу users, Book — в books. Поле с именем ID воспринимается как первичный ключ. Остальные поля автоматически превращаются в столбцы. Для каждого поля библиотека подбирает тип в базе: string сопоставляется с текстовым типом, целые числа — с INTEGER, время — с TIMESTAMP и аналогичными типами СУБД. Такая автоматика закрывает большинство базовых сценариев без ручного SQL.

Простейшая модель пользователя и миграция таблицы могут выглядеть так:

package main

import (
	"log"

	"gorm.io/driver/postgres"
	"gorm.io/gorm"
)

// User описывает сущность «пользователь».
// GORM создаст по этой структуре таблицу users.
type User struct {
	ID    uint   // первичный ключ
	Name  string // имя
	Email string // электронная почта
	Age   int    // возраст
}

func main() {
	dsn := "host=localhost user=postgres password=postgres dbname=testdb port=5432 sslmode=disable"

	// Подключение к базе через GORM и драйвер postgres.
	db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
	if err != nil {
		log.Fatalf("ошибка подключения: %v", err)
	}

	// Автоматическая миграция: создание или обновление таблицы users.
	if err := db.AutoMigrate(&User{}); err != nil {
		log.Fatalf("ошибка миграции: %v", err)
	}

	log.Println("Таблица users готова")
}

После запуска такого кода GORM создаёт таблицу users с колонками id, name, email и age, если её ещё не существует. Дальше та же структура используется для работы с данными. Вставка записи и чтение из базы укладываются в несколько строк:

// Добавление новой записи в таблицу users.
db.Create(&User{Name: "Анна", Email: "anna@mail.com", Age: 25})

// Выборка первой записи по первичному ключу.
var user User
db.First(&user, 1)

// Поля структуры заполнены данными из базы.
log.Println("Имя пользователя:", user.Name)

GORM формирует INSERT и SELECT сам, подставляет значения параметров и раскладывает результат по полям структуры. Код остаётся на уровне предметной области и не опускается до текстовых запросов.

Имя таблицы по умолчанию удобно не всегда. Когда в существующей базе используются нестандартные наименования, GORM нужно подсказать правильное имя. Для этого структура может объявить метод TableName:

// User связан с таблицей app_users, а не со стандартной users.
func (User) TableName() string {
	return "app_users"
}

Теперь все операции с моделью User будут обращаться к таблице app_users. Такой приём помогает адаптировать ORM к уже существующей схеме без её переписывания.

Точнее управлять отображением полей в таблицу позволяют теги GORM. Эти теги описываются в обратных кавычках после объявления поля и задают имя столбца, тип, ограничения и ключи. Пример модели с настроенными тегами показывает, как структура управляет схемой базы:

// User с максимумом контроля над полями и ограничениями.
type User struct {
	ID       uint   `gorm:"primaryKey"`                           // Явное указание на первичный ключ.
	Username string `gorm:"column:login;size:50;unique;not null"` // Имя столбца login, длина 50, уникальное и не NULL.
	Email    string `gorm:"type:varchar(100)"`                    // Явный тип столбца для email.
	Age      int    `gorm:"default:18"`                           // Значение по умолчанию.
}

В этом описании primaryKey делает поле ID первичным ключом. Параметр column задаёт имя столбца login вместо стандартного username. size ограничивает длину строки, unique добавляет уникальный индекс, not null запрещает пустые значения. Параметр type переопределяет тип столбца в базе, если поведение по умолчанию не подходит. Значение default определяет значение по умолчанию для Age. После AutoMigrate() GORM строит схему c учётом всех этих указаний, и необходимость писать CREATE TABLE вручную исчезает.

Связи между таблицами также управляются через теги. Внешний ключ и поведение при обновлении и удалении можно описать в самой модели. Пример с заказами показывает такую настройку:

// Order с явным внешним ключом на User.
type Order struct {
	ID     uint
	UserID uint `gorm:"not null;index"` // Поле внешнего ключа, индекс и запрет NULL.
	User   User `gorm:"foreignKey:UserID;references:ID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL"`
}

Поле UserID хранит идентификатор пользователя и получает ограничения NOT NULL и INDEX. Поле User задаёт связь: foreignKey указывает, что для связи используется UserID, параметр references объявляет, на какое поле в структуре User идёт ссылка. А constraint описывает поведение СУБД: при обновлении ключа в таблице пользователей заказы обновятся каскадно, а при удалении пользователя ссылки в заказах превратятся в NULL. Такая конфигурация задаётся один раз в коде и затем транслируется в схему базы.

Глобальное управление именами таблиц возможно через стратегию именования. Если все таблицы должны иметь общий префикс, его удобно задать в конфигурации GORM:

import "gorm.io/gorm/schema"

db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
	NamingStrategy: schema.NamingStrategy{
		TablePrefix: "app_", // ко всем таблицам будет добавлен префикс app_
	},
})

После этого User связан с app_users, Order() — с app_orders и так далее. Такой подход выравнивает схему по единым правилам и помогает избежать конфликтов имён.

Отдельную роль играют служебные поля CreatedAt, UpdatedAt и DeletedAt. Большинство таблиц нуждается в информации о том, когда запись создана, когда изменена и была ли удалена логически. GORM умеет управлять такими полями автоматически, если они присутствуют в структуре. Поле CreatedAt заполняется при создании записи. UpdatedAt меняется при каждом обновлении. DeletedAt применяется для «мягкого удаления», когда запись остаётся в таблице, но помечается как скрытая.

Пример модели с временными полями и мягким удалением выглядит так:

import (
	"time"

	"gorm.io/gorm"
)

// User с автоматическими полями времени и мягким удалением.
type User struct {
	ID        uint
	Name      string
	Email     string
	CreatedAt time.Time      // Время создания записи.
	UpdatedAt time.Time      // Время последнего обновления.
	DeletedAt gorm.DeletedAt `gorm:"index"` // Пометка времени удаления и индекс по этому полю.
}

При создании новой записи через Create() GORM автоматически заполняет CreatedAt и UpdatedAt текущим временем. При каждом обновлении через Update() или Save() поле UpdatedAt меняется, а CreatedAt остаётся неизменным. При удалении через db.Delete запись не исчезает физически: GORM устанавливает в DeletedAt текущий момент времени, а при последующих выборках с обычными методами такие строки больше не попадают в результаты. Чтобы увидеть все записи, включая помеченные как удалённые, используется Unscoped(). Для физического удаления строк с ненужными данными также применяется Unscoped() вместе с Delete().

Для удобства GORM предоставляет встроенный тип gorm.Model. Этот тип уже включает в себя стандартный набор полей: ID, CreatedAt, UpdatedAt и DeletedAt. Любая модель, встраивающая gorm.Model, автоматически получает эти поля и стандартное поведение:

// Product использует встроенный gorm.Model со служебными полями.
type Product struct {
	gorm.Model        // ID, CreatedAt, UpdatedAt, DeletedAt
	Name       string // название товара
	Price      int    // цена
}

В этом случае нет необходимости отдельно описывать служебные поля: GORM управляет ими по тем же правилам, что и в явном варианте. Такой подход упрощает код и делает модели однородными.

Работа с моделями в GORM становится надёжной, когда структура действительно отражает схему, а теги аккуратно задают ключи, типы и связи. Структуры выполняют роль контракта между приложением и базой, а ORM берёт на себя преобразование этого контракта в реальные таблицы и запросы. При изменении модели GORM помогает постепенно эволюционировать схему, не вынося каждый шаг в отдельный ручной SQL.

Рекомендуемые программы

+7 800 100 22 47

бесплатно по РФ

+7 495 085 21 62

бесплатно по Москве

108813 г. Москва, вн.тер.г. поселение Московский,
г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3
ОГРН 1217300010476
ИНН 7325174845