Object-Relational Mapping (ORM) в Golang

от автора

Одна из самых важных задач ЯП`s это эффективное взаимодействие с базами данных и Go не исключение. В Go есть парадигма Object-Relational Mapping (ORM), позволяет работать с реляционными базами данных в терминах объектно-ориентированного программирования. Это очень сильно упрощает работу с базами данных, позволяя сосредоточиться на бизнес-логике приложения, а не на нюансах SQL-запросов.

GORM (Go Object-Relational Mapping) предоставляет удобный интерфейс для взаимодействия с различными базами данных, сохраняя при этом идиоматичность и фичу конкуретности в Go.

Установка и настройка GORM

Естественно, для работы необходим непосредственно сам go :d.

GORM поддерживает множество БД: MySQL, PostgreSQL, SQLite. Настройка подключения зависит от выбранной вами СУБД.

Для MySQL:

import (     "gorm.io/gorm"     "gorm.io/driver/mysql" )  func main() {     dsn := "user:password@tcp(localhost:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"     db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) }

Для PostgreSQL:

import (     "gorm.io/gorm"     "gorm.io/driver/postgres" )  func main() {     dsn := "host=localhost user=user dbname=db password=password sslmode=disable"     db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) } 

Для SQLite:

import (     "gorm.io/gorm"     "gorm.io/driver/sqlite" )  func main() {     db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{}) } 

После успешного коннетка к БД, вы можете начать определять модели и работать с ними. Например, создание модели User и выполнение миграций выглядит так:

type User struct {     gorm.Model     Name  string     Email string `gorm:"uniqueIndex"` }  func main() {     // Подключение к базе данных...     db.AutoMigrate(&User{})     // Дальнейшие операции с базой данных }

Код автоматически создаст таблицу users в вашей базе данных, если она еще не существует. А про миграцию чуть попозже.

Определение моделей в GORM

В GORM, каждая структура Go (struct) представляет собой таблицу в базе данных. Поля структуры соответствуют столбцам в таблице.

Теги GORM используются для указания дополнительных деталей о полях структуры, таких как ограничения, индексы и типы данных.

type User struct {     gorm.Model     Name   string `gorm:"size:255"`     Email  string `gorm:"type:varchar(100);unique_index"`     Age    int    `gorm:"default:18"` }

gorm.Model добавляет стандартные поля ID, CreatedAt, UpdatedAt, DeletedAt. size:255 и type:varchar(100) определяют типы данных и размеры столбцов. unique_index создает уникальный индекс для поля Email.

GORM автоматически рассматривает поле с именем ID как первичный ключ. Можно определить другой первичный ключ с помощью тега primaryKey.

GORM поддерживает определение отношений между таблицами, таких как hasOne, hasMany, belongsTo, many2many. Пример:

type User struct {     gorm.Model     CreditCards []CreditCard `gorm:"foreignKey:UserID"` }  type CreditCard struct {     gorm.Model     Number string     UserID uint }

CreditCards в User определяет отношение один ко многим с таблицей CreditCard.

Можно встраивать одну структуру в другую. Это полезно для повторного использования общих полей.

Если вы хотите использовать специфическое имя таблицы вместо стандартного (которое GORM генерирует из имени структуры), можно определить метод TableName.

GORM облегчает миграцию схемы базы данных с использованием функции AutoMigrate.

GORM позволяет определить пользовательские типы данных, которые могут быть преобразованы из Go типов в типы, понятные базе данных.

Можно использовать условные теги для определения полей, которые должны присутствовать только при определенных условиях:

primaryKey: указывает, что поле является первичным ключом:

ID uint `gorm:"primaryKey"`

unique: создает уникальный индекс для поля, обеспечивая уникальность значений в нем:

Email string `gorm:"unique"`

size: определяет максимальный размер поля (для строк):

Name string `gorm:"size:255"`

type: указывает конкретный тип данных для поля в базе данных:

Description string `gorm:"type:text"`

index: создает индекс для поля или группы полей:

CreatedAt time.Time `gorm:"index"`

index:idx_name,sort:desc: создает индекс с указанным именем и порядком сортировки:

Age int `gorm:"index:idx_age,sort:desc"`

Определение индекса для нескольких полей:

goCopy codetype Product struct {     ID    uint     Name  string `gorm:"index:idx_name_code"`     Code  string `gorm:"index:idx_name_code"` }

default: устанавливает значение по умолчанию для поля.

goCopy codeActive bool `gorm:"default:true"`

Неявные индексы (Implicit Indexes): GORM автоматически создает индексы для полей, отмеченных как unique и primaryKey

CRUDи прочие операции

Создание

Операция создания в GORM включает добавление новых записей в базу данных. Процесс начинается с создания экземпляра модели и последующего использования метода Create.

user := User{Name: "John Doe", Age: 30} result := db.Create(&user) // создает новую запись в базе данных  if result.Error != nil {     // обработка ошибки создания }

Чтение

Операции чтения включают извлечение данных из базы данных. GORM предоставляет различные методы для этого, включая First, Find, Take.

  • Использование First для получения первой записи:

    var user User db.First(&user)
  • Использование Find для получения всех записей:

    var users []User db.Find(&users)
  • Использование Take для получения одной записи:

    var user User db.Take(&user)

Обновление

Обновление включает изменение существующих записей в базе данных. Методы Save и Updates обычно используются для этой цели.

  • Обновление всех полей записи:

    db.Model(&user).Updates(User{Name: "Perry Utkonos", Age: 25})
  • Частичное обновление полей записи:

    db.Model(&user).Updates(map[string]interface{}{"Name": "Perry Utkonos", "Age": 25})

Удаление

Удаление удаляет записи из базы данных. Метод Delete используется для удаления одной или нескольких записей.

Пример:

db.Delete(&user, 1) // Удаляет запись с ID 1 

В дополнение к базовым CRUD-операциям, GORM позволяет выполнить ряд продвинутых действий, таких как:

Пакетные операции

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

Массовое добавление: позволяет добавить множество записей одним запросом, к примеру:

var users []User // Добавление данных в users db.Create(&users)

Массовое обновление: обновление множества записей на основе определенных критериев:

db.Model(&User{}).Where("active = ?", true).Updates(map[string]interface{}{"active": false})

Транзакции

Если одна из операций в транзакции не удается, вся транзакция откатывается, что гарантирует целостность данных.

К примеру транзакция может выглядеть так:

tx := db.Begin() defer func() {     if r := recover(); r != nil {         tx.Rollback()     } }()  if tx.Error != nil {     return // Возвращает ошибку при неудачной инициализации транзакции }  // Последовательность операций if err := tx.Create(&newUser).Error; err != nil {     tx.Rollback()     return }  if err := tx.Update(&existingUser).Error; err != nil {     tx.Rollback()     return }  tx.Commit()

Предварительная загрузка

Предварительная загрузка в GORM позволяет автоматически загружать связанные данные, предотвращая проблему N+1 запросов, когда для каждой записи выполняется отдельный запрос для загрузки связанных данных.

Допустим, у нас есть модели User и Profile. Мы хотим загрузить пользователей вместе с их профилями:

var users []User db.Preload("Profile").Find(&users)

Preload автоматически загружает связанные данные для каждого пользователя, уменьшая общее количество запросов к базе данных.

Ассоциации и отношения в моделях

«Has One» ассоциация: Это тип отношения, где одна модель явно связана с одной экземпляром другой модели. Например, если у нас есть модель User и Profile, каждый пользователь может иметь только один профиль. В GORM это отношение определяется путем добавления поля Profile в структуру User, и GORM автоматически управляет связью между этими моделями.

Реализуем в коде:

type User struct {     gorm.Model     Name    string     Profile Profile }  type Profile struct {     gorm.Model     UserID uint     Bio    string }  // Создание пользователя с профилем user := User{Name: "Perry Utkonos", Profile: Profile{Bio: "strange utkonos"}} db.Create(&user)

«Has Many» ассоциация: Этот тип отношения используется, когда одна модель связана с несколькими экземплярами другой модели. Например, модель User может иметь несколько Orders. В этом случае в структуре User будет поле, представляющее собой срез экземпляров Order. GORM обеспечивает удобный доступ к этим связанным данным и позволяет легко выполнять операции с ними.

В коде:

type User struct {     gorm.Model     Name   string     Orders []Order }  type Order struct {     gorm.Model     UserID uint     Item   string }  // добавление заказов пользователю var orders = []Order{{Item: "Item 1"}, {Item: "Item 2"}} user := User{Name: "Jane Doe", Orders: orders} db.Create(&user)

«Belongs To» ассоциация: Это обратная сторона отношения «Has One» и «Has Many». В этом случае подчиненная модель содержит ссылку на родительскую модель. Например, если у нас есть Order, который принадлежит User, в структуре Order будет поле, указывающее на User. Это позволяет легко навигировать от подчиненной модели к родительской.

В коде:

type User struct {     gorm.Model     Name string }  type Order struct {     gorm.Model     Item   string     UserID uint     User   User }  // создание заказа для пользователя user := User{Name: "Alice"} db.Create(&user) order := Order{Item: "Book", UserID: user.ID} db.Create(&order)

«Many-to-Many» ассоциации: Этот тип отношения используется, когда несколько экземпляров одной модели связаны с несколькими экземплярами другой модели. Например, если у нас есть модели Student и Course, студент может посещать несколько курсов, и на каждом курсе может быть несколько студентов. В GORM для управления этими отношениями используется промежуточная таблица, которая связывает экземпляры двух моделей:

type Student struct {     gorm.Model     Name    string     Courses []Course `gorm:"many2many:student_courses;"` }  type Course struct {     gorm.Model     Title    string     Students []Student `gorm:"many2many:student_courses;"` }  course := Course{Title: "Biology"} student := Student{Name: "Bob"} db.Create(&course) db.Create(&student)  db.Model(&student).Association("Courses").Append(&course)

Другие возможности Gorm

Работа с транзакциями

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

tx := db.Begin()  // Выполнение операций в рамках транзакции if err := tx.Create(&newRecord).Error; err != nil {    tx.Rollback()    return err }  // Подтверждение транзакции tx.Commit()

Хуки

GORM предоставляет хуки (callbacks), которые позволяют вмешаться в процесс выполнения операций, таких как добавление, обновление или удаление записей.

К примеру можно определить функции, которые будут вызываться до или после определенных операций (например, BeforeCreate, AfterCreate).

Области запросов (Scopes)

Области запросов позволяют переиспользовать определенные условия запросов, делая код чище и более поддерживаемым.

func ActiveUsers(db *gorm.DB) *gorm.DB {     return db.Where("active = ?", true) }  db.Scopes(ActiveUsers).Find(&users)

Построители запросов

Также в GORM доступные стандартные sql запросы с условиями, группировками, сортировками и так далее.

Использование методов Where, Order, Limit для формирования запросов.

Пагинация и сортировка данных

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

package main  import (     "gorm.io/gorm"     "gorm.io/driver/sqlite"     "log" )  type Product struct {     gorm.Model     Code  string     Price uint }  func main() {     db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})     if err != nil {         log.Fatal("failed to connect database")     }      // автлматическая миграция схемы     db.AutoMigrate(&Product{})      // Пагинация и сортировка     var products []Product     pageSize := 10  // Количество записей на страницу     page := 1       // Номер страницы     db.Order("price desc").Offset((page - 1) * pageSize).Limit(pageSize).Find(&products)      // products теперь содержит страницу продуктов, отсортированных по убыванию цены }

Миграции

Функция AutoMigrate автоматизирует процесс создания и обновления таблиц, основанных на структурах Go.

AutoMigrate принимает структуры Go и создает или обновляет соответствующие таблицы в базе данных. Каждое поле в структуре Go интерпретируется как столбец в таблице.

Если таблица, соответствующая структуре, не существует в базе данных, AutoMigrate создает её. Это включает в себя определение всех столбцов согласно полям структуры и их типам.

Если таблица уже существует, AutoMigrate проверяет структуру таблицы и обновляет её, чтобы соответствовать структуре Go. Это может включать добавление новых столбцов или изменение существующих. Однако, AutoMigrate не удаляет столбцы и не изменяет типы данных существующих столбцов.

AutoMigrate также управляет созданием индексов и ограничений на основе тегов, определенных в структурах Go.

Пример:

// Автоматическая миграция схемы db.AutoMigrate(&Product{})  // Пагинация и сортировка var products []Product pageSize := 10  // Количество записей на страницу page := 1       // Номер страницы db.Order("price desc").Offset((page - 1) * pageSize).Limit(pageSize).Find(&products)  // products теперь содержит страницу продуктов, отсортированных по убыванию цены 

В кодеAutoMigrate будет использоваться для создания или обновления таблицы users в базе данных SQLite. Таблица будет содержать столбцы id, created_at, updated_at, deleted_at (предоставлены gorm.Model), name и email, причем для email будет создан уникальный индекс.

AutoMigrate не удаляет столбцы и не изменяет типы данных существующих столбцов. Это предотвращает потенциальную потерю данных. Использование AutoMigrate в продакшен-среде требует осторожности, поскольку неожиданные изменения схемы могут привести к проблемам.


Конечно, существуют еще прочие возможности для работы с БД с go, к примеру SQLC, SQLX, Beego, GORP, Go-firestorm и SQLBoile. Однако GORM в целом имеет больше возможностей :).

В завершение хочу напомнить о том, что у моих коллег из OTUS есть ряд экспертных курсов по программированию. Заходите в каталог и выбирайте интересующий вас курс.


ссылка на оригинал статьи https://habr.com/ru/articles/780902/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *