ORM-библиотека Gorm
Официальная документация: GORM - The fantastic ORM library for Golang, aims to be developer friendly.
Репозиторий: go-gorm/gorm: The fantastic ORM library for Golang, aims to be developer friendly (github.com)
В сообществе Go для взаимодействия с базой данных существуют два лагеря. Один лагерь предпочитает более простые библиотеки, такие как sqlx, которые не так мощны, но позволяют полностью контролировать SQL и оптимизировать производительность до предела. Другой лагерь предпочитает ORM для эффективности разработки, что может сэкономить много ненужных проблем во время разработки. Когда речь заходит об ORM, в сообществе Go абсолютно невозможно обойти gorm. Это очень хорошо зарекомендовавшая себя ORM, похожая на относительно молодые, такие как xorm, ent и т.д. Эта статья о gorm. Эта статья объясняет только её базовое вводное содержание, просто как отправная точка. Чтобы узнать больше деталей, вы можете прочитать официальную документацию. Её китайская документация довольно полная, и автор также является одним из переводчиков документации gorm.
Особенности
- Полнофункциональная ORM
- Ассоциации (Has One, Has Many, Belongs To, Many To Many, Полиморфизм, Наследование одной таблицы)
- Хуки (Before/After Create/Save/Update/Delete/Find)
- Предзагрузка с жадной загрузкой (Preload, Joins)
- Транзакции, вложенные транзакции, точки сохранения, откат к сохранённым точкам
- Контекст, режим подготовленных выражений, режим DryRun
- Пакетная вставка, FindInBatches, Find/Create с Map, CRUD с SQL-выражениями, Context Valuer
- SQL Builder, Upsert, блокировка, подсказки Optimizer/Index/Comment, именованные параметры, подзапросы
- Составной первичный ключ, индекс, ограничение
- Автоматическая миграция
- Пользовательский логгер
- Гибкий расширяемый API плагинов: Database Resolver (несколько баз данных, разделение чтения/записи), Prometheus...
- Каждая функция тщательно протестирована
- Удобство для разработчика
У Gorm есть некоторые недостатки. Например, почти все параметры методов имеют пустой тип интерфейса. Без просмотра документации вы, вероятно, не узнаете, что передавать. Иногда можно передать struct, иногда строку, иногда map, иногда slice. Семантика несколько неопределённа, и во многих случаях всё ещё нужно писать SQL вручную.
В качестве альтернатив есть две ORM, которые можно попробовать. Первая — aorm, которая была открыта не так давно. Она больше не требует писать имена полей таблиц вручную, в основном использует цепочечные операции и основана на рефлексии. Поскольку количество звёзд невелико, можно подождать и посмотреть. Вторая — ent, открытая facebook. Она также поддерживает цепочечные операции, и в большинстве случаев не нужно писать SQL вручную. Её философия дизайна основана на графах (из структур данных), а реализация основана на генерации кода, а не на рефлексии (с чем я больше согласен). Однако документация только на английском, что имеет определённый порог обучения.
Установка
Установите библиотеку gorm
$ go get -u gorm.io/gormПодключение
Gorm в настоящее время поддерживает следующие базы данных
- MySQL:
"gorm.io/driver/mysql" - PostgreSQL:
"gorm.io/driver/postgres" - SQLite:
"gorm.io/driver/sqlite" - SQL Server:
"gorm.io/driver/sqlserver" - TIDB:
"gorm.io/driver/mysql", TIDB совместим с протоколом mysql - ClickHouse:
"gorm.io/driver/clickhouse"
Кроме того, есть некоторые другие драйверы баз данных, предоставленные сторонними разработчиками, такие как драйвер oracle CengSin/oracle. В этой статье для демонстрации будет использоваться MySQL. Какую бы базу данных вы ни использовали, нужно установить соответствующий драйвер. Здесь мы устанавливаем драйвер gorm для MySQL.
$ go get -u gorm.io/driver/mysqlЗатем подключаемся к базе данных, используя dsn (имя источника данных). Библиотека драйвера автоматически разберёт dsn в соответствующую конфигурацию.
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"log/slog"
)
func main() {
dsn := "root:123456@tcp(192.168.48.138:3306)/hello?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn))
if err != nil {
slog.Error("ошибка подключения к БД", err)
}
slog.Info("подключение к БД успешно")
}Или вручную передать конфигурацию
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"log/slog"
)
func main() {
db, err := gorm.Open(mysql.New(mysql.Config{}))
if err != nil {
slog.Error("ошибка подключения к БД", err)
}
slog.Info("подключение к БД успешно")
}Оба метода эквивалентны; это зависит от вашего предпочтения использования.
Конфигурация подключения
Передавая структуру конфигурации gorm.Config, мы можем контролировать некоторые поведения gorm.
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})Ниже приведены некоторые простые объяснения. Вы можете настроить в соответствии с вашими потребностями.
type Config struct {
// Отключить транзакцию по умолчанию, gorm запустит транзакцию для одиночного create и update для поддержания согласованности данных
SkipDefaultTransaction bool
// Пользовательская стратегия именования
NamingStrategy schema.Namer
// Сохранить полные ассоциации
FullSaveAssociations bool
// Пользовательский логгер
Logger logger.Interface
// Пользовательская nowfunc, используется для внедрения полей CreatedAt и UpdatedAt
NowFunc func() time.Time
// Генерировать только SQL без выполнения
DryRun bool
// Использовать подготовленные выражения
PrepareStmt bool
// После установления подключения, ping базы данных
DisableAutomaticPing bool
// Игнорировать внешние ключи при миграции базы данных
DisableForeignKeyConstraintWhenMigrating bool
// Игнорировать ссылки на ассоциации при миграции базы данных
IgnoreRelationshipsWhenMigrating bool
// Отключить вложенные транзакции
DisableNestedTransaction bool
// Разрешить глобальное обновление, обновление без where clause
AllowGlobalUpdate bool
// Запросить все поля таблицы
QueryFields bool
// Размер пакетного создания
CreateBatchSize int
// Включить перевод ошибок
TranslateError bool
// ClauseBuilders построитель clause
ClauseBuilders map[string]clause.ClauseBuilder
// ConnPool пул подключений к БД
ConnPool ConnPool
// Dialector диалектор базы данных
Dialector
// Plugins зарегистрированные плагины
Plugins map[string]Plugin
callbacks *callbacks
cacheStore *sync.Map
}Модель
В gorm модель соответствует таблице базы данных. Обычно она представлена struct, например, следующий struct.
type Person struct {
Id uint
Name string
Address string
Mom string
Dad string
}Внутренние поля struct могут состоять из базовых типов данных и типов, реализующих интерфейсы sql.Scanner и sql.Valuer. По умолчанию имя таблицы, сопоставленной struct Person, — persons, это стиль snake_case во множественном числе, разделённый подчёркиваниями. Имена столбцов также в стиле snake_case, например, Id соответствует имени столбца id. Gorm также предоставляет некоторые способы настройки этого.
Указание имён столбцов
Через теги struct мы можем указать имена столбцов для полей struct. Таким образом, во время сопоставления сущностей gorm будет использовать указанные имена столбцов.
type Person struct {
Id uint `gorm:"column:ID;"`
Name string `gorm:"column:Name;"`
Address string
Mom string
Dad string
}Указание имён таблиц
Реализуя интерфейс Table, вы можете указать имя таблицы. У него только один метод, который возвращает имя таблицы.
type Tabler interface {
TableName() string
}В реализованном методе возвращается строка person. Во время миграции базы данных gorm создаст таблицу с именем person.
type Person struct {
Id uint `gorm:"column:ID;"`
Name string `gorm:"column:Name;"`
Address string
Mom string
Dad string
}
func (p Person) TableName() string {
return "person"
}Для стратегий именования вы также можете передать свою реализацию стратегии при создании подключения для достижения пользовательских эффектов.
Отслеживание времени
type Person struct {
Id uint
Name string
Address string
Mom string
Dad string
CreatedAt sql.NullTime
UpdatedAt sql.NullTime
}
func (p Person) TableName() string {
return "person"
}При наличии полей CreatedAt или UpdatedAt, во время создания или обновления записи, если их нулевое значение, gorm автоматически использует time.Now() для установки времени.
db.Create(&Person{
Name: "jack",
Address: "usa",
Mom: "lili",
Dad: "tom",
})
// INSERT INTO `person` (`name`,`address`,`mom`,`dad`,`created_at`,`updated_at`) VALUES ('jack','usa','lili','tom','2023-10-25 14:43:57.16','2023-10-25 14:43:57.16')Gorm также поддерживает отслеживание временных меток
type Person struct {
Id uint `gorm:"primaryKey;"`
Name string `gorm:"primaryKey;"`
Address string
Mom string
Dad string
// наносекунды
CreatedAt uint64 `gorm:"autoCreateTime:nano;"`
// миллисекунды
UpdatedAt uint64 `gorm:"autoUpdateTime;milli;"`
}Тогда во время выполнения Create это эквивалентно следующему SQL
INSERT INTO `person` (`name`,`address`,`mom`,`dad`,`created_at`,`updated_at`) VALUES ('jack','usa','lili','tom',1698216540519000000,1698216540)В реальных ситуациях, если вам нужно отслеживание времени, я рекомендую хранить временные метки на бэкенде, что проще обрабатывать в сценариях с несколькими часовыми поясами.
Модель
Gorm предоставляет предустановленную структуру Model, которая содержит первичный ключ ID, два поля отслеживания времени и поле записи мягкого удаления.
type Model struct {
ID uint `gorm:"primarykey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt DeletedAt `gorm:"index"`
}Для использования просто встройте её в вашу модель сущности.
type Order struct {
gorm.Model
Name string
}Таким образом, она автоматически получит все функции gorm.Model.
Первичный ключ
По умолчанию поле с именем Id является первичным ключом. Вы можете использовать теги struct для указания поля первичного ключа.
type Person struct {
Id uint `gorm:"primaryKey;"`
Name string
Address string
Mom string
Dad string
CreatedAt sql.NullTime
UpdatedAt sql.NullTime
}Несколько полей образуют составной первичный ключ
type Person struct {
Id uint `gorm:"primaryKey;"`
Name string `gorm:"primaryKey;"`
Address string
Mom string
Dad string
CreatedAt sql.NullTime
UpdatedAt sql.NullTime
}Индекс
Вы можете указать индексы столбцов через тег struct index
type Person struct {
Id uint `gorm:"primaryKey;"`
Name string `gorm:"primaryKey;"`
Address string `gorm:"index:idx_addr,unique,sort:desc;"`
Mom string
Dad string
// наносекунды
CreatedAt uint64 `gorm:"autoCreateTime:nano;"`
// миллисекунды
UpdatedAt uint64 `gorm:"autoUpdateTime;milli;"`
}В приведённом struct для поля Address создаётся уникальный индекс. Два поля, использующие одно и то же имя индекса, создадут составной индекс
type Person struct {
Id uint `gorm:"primaryKey;"`
Name string `gorm:"primaryKey;"`
Address string `gorm:"index:idx_addr,unique;"`
School string `gorm:"index:idx_addr,unique;"`
Mom string
Dad string
// наносекунды
CreatedAt uint64 `gorm:"autoCreateTime:nano;"`
// миллисекунды
UpdatedAt uint64 `gorm:"autoUpdateTime;milli;"`
}Внешний ключ
В struct отношения внешних ключей определяются путём встраивания struct, например
type Person struct {
Id uint `gorm:"primaryKey;"`
Name string
MomId uint
Mom Mom `gorm:"foreignKey:MomId;"`
DadId uint
Dad Dad `gorm:"foreignKey:DadId;"`
}
type Mom struct {
Id uint
Name string
Persons []Person `gorm:"foreignKey:MomId;"`
}
type Dad struct {
Id uint
Name string
Persons []Person `gorm:"foreignKey:DadId;"`
}В примере struct Person имеет два внешних ключа, ссылающихся на первичные ключи struct Dad и Mom соответственно. По умолчанию он ссылается на первичный ключ. Person имеет отношение один-к-одному с Dad и Mom — у человека может быть только один отец и одна мать. Dad и Mom имеют отношение один-ко-многим с Person, потому что у отцов и матерей может быть несколько детей.
Mom Mom `gorm:"foreignKey:MomId;"`Цель встраивания struct — облегчить указание внешних ключей и ссылок. По умолчанию формат имени поля внешнего ключа — ReferencedTypeName+Id, например, MomId. По умолчанию он ссылается на первичный ключ. Вы можете указать поле для ссылки через теги struct
type Person struct {
Id uint `gorm:"primaryKey;"`
Name string
MomId uint
Mom Mom `gorm:"foreignKey:MomId;references:Sid;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
DadId uint
Dad Dad `gorm:"foreignKey:DadId;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}
type Mom struct {
Id uint
Sid uint `gorm:"uniqueIndex;"`
Name string
Persons []Person `gorm:"foreignKey:MomId;"`
}Среди них constraint:OnUpdate:CASCADE,OnDelete:SET NULL; определяет ограничение внешнего ключа.
Хуки
Модель сущности может настраивать хуки
- Create
- Update
- Delete
- Query
Соответствующие интерфейсы следующие
// Вызывается перед create
type BeforeCreateInterface interface {
BeforeCreate(*gorm.DB) error
}
// Вызывается после create
type AfterCreateInterface interface {
AfterCreate(*gorm.DB) error
}
// Вызывается перед update
type BeforeUpdateInterface interface {
BeforeUpdate(*gorm.DB) error
}
// Вызывается после update
type AfterUpdateInterface interface {
AfterUpdate(*gorm.DB) error
}
// Вызывается перед save
type BeforeSaveInterface interface {
BeforeSave(*gorm.DB) error
}
// Вызывается после save
type AfterSaveInterface interface {
AfterSave(*gorm.DB) error
}
// Вызывается перед delete
type BeforeDeleteInterface interface {
BeforeDelete(*gorm.DB) error
}
// Вызывается после delete
type AfterDeleteInterface interface {
AfterDelete(*gorm.DB) error
}
// Вызывается после query
type AfterFindInterface interface {
AfterFind(*gorm.DB) error
}Struct могут настраивать поведения, реализуя эти интерфейсы.
Теги
Ниже приведены некоторые теги, поддерживаемые gorm
| Имя тега | Описание |
|---|---|
column | Указывает имя столбца БД |
type | Тип данных столбца, рекомендуется использовать совместимые универсальные типы, например, все базы данных поддерживают bool, int, uint, float, string, time, bytes и могут использоваться с другими тегами, например, not null, size, autoIncrement... Также поддерживается указание типов данных БД, таких как varbinary(8). При использовании указанных типов данных БД это должен быть полный тип данных БД, например, MEDIUMINT UNSIGNED not NULL AUTO_INCREMENT |
serializer | Указывает сериализатор для сериализации или десериализации данных в/из БД, например, serializer:json/gob/unixtime |
size | Определяет размер или длину типа данных столбца, например, size: 256 |
primaryKey | Определяет столбец как первичный ключ |
unique | Определяет столбец как уникальный ключ |
default | Определяет значение по умолчанию столбца |
precision | Указывает точность столбца |
scale | Указывает масштаб столбца |
not null | Указывает столбец как NOT NULL |
autoIncrement | Указывает столбец как автоинкремент |
autoIncrementIncrement | Шаг автоинкремента, контролирует интервал между последовательными записями |
embedded | Встроенное поле |
embeddedPrefix | Префикс имени столбца для встроенного поля |
autoCreateTime | Отслеживает текущее время при создании, для полей int отслеживает секунды временной метки, можно использовать nano/milli для отслеживания наносекунд/миллисекунд временной метки, например, autoCreateTime:nano |
autoUpdateTime | Отслеживает текущее время при создании/обновлении, для полей int отслеживает секунды временной метки, можно использовать nano/milli для отслеживания наносекунд/миллисекунд временной метки, например, autoUpdateTime:milli |
index | Создаёт индекс на основе параметров, несколько полей, использующих одно и то же имя, создают составной индекс, подробности см. Indexes |
uniqueIndex | То же, что и index, но создаёт уникальный индекс |
check | Создаёт ограничение проверки, например, check:age > 13, подробности см. Constraints |
<- | Устанавливает разрешение на запись поля, <-:create только создание, <-:update только обновление, <-:false нет разрешения на запись, <- разрешение на создание и обновление |
-> | Устанавливает разрешение на чтение поля, ->:false нет разрешения на чтение |
- | Игнорирует это поле, - означает нет чтения/записи, -:migration означает нет разрешения на миграцию, -:all означает нет чтения/записи/миграции |
comment | Добавляет комментарий для поля во время миграции |
foreignKey | Указывает столбец текущей модели как внешний ключ для таблицы соединения |
references | Указывает имя столбца ссылочной таблицы, который будет сопоставлен как внешний ключ таблицы соединения |
polymorphic | Указывает полиморфный тип, например, имя модели |
polymorphicValue | Указывает полиморфное значение, по умолчанию имя таблицы |
many2many | Указывает имя таблицы соединения |
joinForeignKey | Указывает имя столбца внешнего ключа в таблице соединения, который будет сопоставлен с текущей таблицей |
joinReferences | Указывает имя столбца внешнего ключа в таблице соединения, который будет сопоставлен со ссылочной таблицей |
constraint | Ограничения отношений, например, OnUpdate, OnDelete |
Миграция
Метод AutoMigrate помогает нам с автоматической миграцией. Он создаст таблицы, ограничения, индексы, внешние ключи и т.д.
func (db *DB) AutoMigrate(dst ...interface{}) errorНапример
type Person struct {
Id uint `gorm:"primaryKey;"`
Name string `gorm:"type:varchar(100);uniqueIndex;"`
Address string
}
type Order struct {
Id uint
Name string
}
db.AutoMigrate(Person{}, Order{})
// CREATE TABLE `person` (`id` bigint unsigned AUTO_INCREMENT,`name` varchar(100),`address` longtext,PRIMARY KEY (`id`),UNIQUE INDEX `idx_person_name` (`name`))
// CREATE TABLE `orders` (`id` bigint unsigned AUTO_INCREMENT,`name` longtext,PRIMARY KEY (`id`))Или мы можем оперировать вручную, получив доступ к интерфейсу Migrator через метод Migrator
func (db *DB) Migrator() MigratorОн поддерживает следующие методы интерфейса
type Migrator interface {
// AutoMigrate
AutoMigrate(dst ...interface{}) error
// Database
CurrentDatabase() string
FullDataTypeOf(*schema.Field) clause.Expr
GetTypeAliases(databaseTypeName string) []string
// Tables
CreateTable(dst ...interface{}) error
DropTable(dst ...interface{}) error
HasTable(dst interface{}) bool
RenameTable(oldName, newName interface{}) error
GetTables() (tableList []string, err error)
TableType(dst interface{}) (TableType, error)
// Columns
AddColumn(dst interface{}, field string) error
DropColumn(dst interface{}, field string) error
AlterColumn(dst interface{}, field string) error
MigrateColumn(dst interface{}, field *schema.Field, columnType ColumnType) error
HasColumn(dst interface{}, field string) bool
RenameColumn(dst interface{}, oldName, field string) error
ColumnTypes(dst interface{}) ([]ColumnType, error)
// Views
CreateView(name string, option ViewOption) error
DropView(name string) error
// Constraints
CreateConstraint(dst interface{}, name string) error
DropConstraint(dst interface{}, name string) error
HasConstraint(dst interface{}, name string) bool
// Indexes
CreateIndex(dst interface{}, name string) error
DropIndex(dst interface{}, name string) error
HasIndex(dst interface{}, name string) bool
RenameIndex(dst interface{}, oldName, newName string) error
GetIndexes(dst interface{}) ([]Index, error)
}Список методов включает несколько измерений: база данных, таблица, столбец, представление, индекс, ограничение. Для пользователей, нуждающихся в настройке, это позволяет более детальные операции.
Указание комментариев таблиц
Во время миграции, если вы хотите добавить комментарии к таблице, вы можете установить это следующим образом
db.Set("gorm:table_options", " comment 'person table'").Migrator().CreateTable(Person{})Обратите внимание, что если вы используете метод AutoMigrate() для миграции и между struct есть отношения ссылок, gorm рекурсивно создаст сначала ссылочные таблицы. Это приведёт к тому, что и ссылочная таблица, и ссылающаяся таблица будут иметь дублирующиеся комментарии. Поэтому рекомендуется использовать метод CreateTable для создания.
TIP
При создании таблиц метод CreateTable требует, чтобы ссылочная таблица была создана перед ссылающейся таблицей, иначе будет сообщено об ошибке. Метод AutoMigrate не требует этого, потому что он рекурсивно создаёт таблицы, следуя отношениям ссылок.
Создание
Create
При создании новых записей в большинстве случаев используется метод Create
func (db *DB) Create(value interface{}) (tx *DB)Дан следующий struct
type Person struct {
Id uint `gorm:"primaryKey;"`
Name string
}Создать запись
user := Person{
Name: "jack",
}
// Должна быть передана ссылка
db = db.Create(&user)
// Ошибка, возникшая во время выполнения
err = db.Error
// Количество затронутых строк
affected := db.RowsAffectedПосле создания gorm запишет первичный ключ в struct user, поэтому вы должны передать указатель. Если вы передадите slice, это будет пакетное создание
user := []Person{
{Name: "jack"},
{Name: "mike"},
{Name: "lili"},
}
db = db.Create(&user)Аналогично, gorm запишет первичный ключ в slice. Когда объём данных слишком велик, вы также можете использовать метод CreateInBatches для пакетного создания, потому что сгенерированный SQL INSERT INTO table VALUES (),() станет очень длинным, и у каждой базы данных есть ограничения на длину SQL. Поэтому при необходимости вы можете выбрать пакетное создание.
db = db.CreateInBatches(&user, 50)Кроме того, метод Save также может создавать записи. Его функция — обновлять запись при совпадении первичного ключа, иначе вставлять.
func (db *DB) Save(value interface{}) (tx *DB)user := []Person{
{Name: "jack"},
{Name: "mike"},
{Name: "lili"},
}
db = db.Save(&user)Upsert
Метод Save может сопоставлять только первичные ключи. Мы можем построить более настраиваемый upsert, сконструировав Clause. Например, эта строка кода
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "name"}},
DoNothing: false,
DoUpdates: clause.AssignmentColumns([]string{"address"}),
UpdateAll: false,
}).Create(&p)Её функция: когда поле name конфликтует, обновить значение поля address. Если конфликта нет, будет создана новая запись. Вы также можете ничего не делать при конфликте
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "name"}},
DoNothing: true,
}).Create(&p)Или напрямую обновить все поля
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "name"}},
UpdateAll: true,
}).Create(&p)Перед использованием upsert не забудьте добавить индекс к полю конфликта.
Запрос
First
Для запросов gorm предоставляет довольно много методов. Первый — метод First
func (db *DB) First(dest interface{}, conds ...interface{}) (tx *DB)Его функция — найти первую запись, упорядоченную по первичному ключу по возрастанию, например
var person Person
result := db.First(&person)
err := result.Error
affected := result.RowsAffectedПередайте указатель dest, чтобы gorm сопоставил запрошенные данные со struct.
Или используйте методы Table и Model для указания таблицы запроса. Первый принимает строковое имя таблицы, второй принимает модель сущности.
db.Table("person").Find(&p)
db.Model(Person{}).Find(&p)TIP
Если переданный указатель содержит модель сущности, такую как указатель struct или указатель slice struct, тогда вам не нужно вручную указывать, какую таблицу запрашивать. Это правило применяется ко всем операциям CRUD.
Take
Метод Take похож на First, разница в том, что он не сортирует по первичному ключу.
func (db *DB) Take(dest interface{}, conds ...interface{}) (tx *DB)var person Person
result := db.Take(&person)
err := result.Error
affected := result.RowsAffectedPluck
Метод Pluck используется для пакетного запроса одного столбца из таблицы. Результаты запроса могут быть собраны в slice указанного типа, не обязательно slice типа сущности.
func (db *DB) Pluck(column string, dest interface{}) (tx *DB)Например, собрать адреса всех в string slice
var adds []string
// SELECT `address` FROM `person` WHERE name IN ('jack','lili')
db.Model(Person{}).Where("name IN ?", []string{"jack", "lili"}).Pluck("address", &adds)На самом деле это эквивалентно
db.Select("address").Where("name IN ?", []string{"jack", "lili"}).Find(&adds)Count
Метод Count используется для подсчёта количества записей сущности
func (db *DB) Count(count *int64) (tx *DB)Смотрите пример использования
var count int64
// SELECT count(*) FROM `person`
db.Model(Person{}).Count(&count)Find
Наиболее часто используемый метод для пакетных запросов — метод Find
func (db *DB) Find(dest interface{}, conds ...interface{}) (tx *DB)Он найдёт все записи, соответствующие заданным условиям
// SELECT * FROM `person`
var ps []Person
db.Find(&ps)Select
По умолчанию gorm запрашивает все поля. Мы можем указать поля через метод Select
func (db *DB) Select(query interface{}, args ...interface{}) (tx *DB)Например
// SELECT `address`,`name` FROM `person` ORDER BY `person`.`id` LIMIT 1
db.Select("address", "name").First(&p)Эквивалентно
db.Select([]string{"address", "name"}).First(&p)В то же время вы можете использовать метод Omit для игнорирования полей
func (db *DB) Omit(columns ...string) (tx *DB)Например
// SELECT `person`.`id`,`person`.`name` FROM `person` WHERE id IN (1,2,3,4)
db.Omit("address").Where("id IN ?", []int{1, 2, 3, 4}).Find(&ps)Поля, выбранные или проигнорированные Select и Omit, также будут действовать при создании и обновлении запросов.
Where
Условные запросы используют метод Where
func (db *DB) Where(query interface{}, args ...interface{}) (tx *DB)Вот простой пример
var p Person
db.Where("id = ?", 1).First(&p)Использование нескольких Where в цепочечных операциях построит несколько операторов AND, например
// SELECT * FROM `person` WHERE id = 1 AND name = 'jack' ORDER BY `person`.`id` LIMIT 1
db.Where("id = ?", 1).Where("name = ?", "jack").First(&p)Или используйте метод Or для построения операторов OR
func (db *DB) Or(query interface{}, args ...interface{}) (tx *DB)// SELECT * FROM `person` WHERE id = 1 OR name = 'jack' AND address = 'usa' ORDER BY `person`.`id` LIMIT 1
db.Where("id = ?", 1).
Or("name = ?", "jack").
Where("address = ?", "usa").
First(&p)Также есть метод Not, который похож
func (db *DB) Not(query interface{}, args ...interface{}) (tx *DB)// SELECT * FROM `person` WHERE id = 1 OR name = 'jack' AND NOT name = 'mike' AND address = 'usa' ORDER BY `person`.`id` LIMIT 1
db.Where("id = ?", 1).
Or("name = ?", "jack").
Not("name = ?", "mike").
Where("address = ?", "usa").
First(&p)Для условий IN вы можете напрямую передать slice в метод Where.
db.Where("address IN ?", []string{"cn", "us"}).Find(&ps)Или для условий IN с несколькими столбцами вам нужно использовать тип [][]any для переноса параметров
// SELECT * FROM `person` WHERE (id, name, address) IN ((1,'jack','uk'),(2,'mike','usa'))
db.Where("(id, name, address) IN ?", [][]any{{1, "jack", "uk"}, {2, "mike", "usa"}}).Find(&ps)Gorm поддерживает группировку where, которая комбинирует вышеуказанные операторы
db.Where(
db.Where("name IN ?", []string{"cn", "uk"}).Where("id IN ?", []uint{1, 2}),
).Or(
db.Where("name IN ?", []string{"usa", "jp"}).Where("id IN ?", []uint{3, 4}),
).Find(&ps)
// SELECT * FROM `person` WHERE (name IN ('cn','uk') AND id IN (1,2)) OR (name IN ('usa','jp') AND id IN (3,4))Order
Сортировка использует метод Order
func (db *DB) Order(value interface{}) (tx *DB)Смотрите пример использования
var ps []Person
// SELECT * FROM `person` ORDER BY name ASC, id DESC
db.Order("name ASC, id DESC").Find(&ps)Вы также можете вызвать его несколько раз
// SELECT * FROM `person` ORDER BY name ASC, id DESC, address
db.Order("name ASC, id DESC").Order("address").Find(&ps)Limit
Методы Limit и Offset часто используются для запросов с пагинацией
func (db *DB) Limit(limit int) (tx *DB)
func (db *DB) Offset(offset int) (tx *DB)Вот простой пример пагинации
var (
ps []Person
page = 2
size = 10
)
// SELECT * FROM `person` LIMIT 10 OFFSET 10
db.Offset((page - 1) * size).Limit(size).Find(&ps)Group
Методы Group и Having в основном используются для операций группировки
func (db *DB) Group(name string) (tx *DB)
func (db *DB) Having(query interface{}, args ...interface{}) (tx *DB)Смотрите пример
var (
ps []Person
)
// SELECT `address` FROM `person` GROUP BY `address` HAVING address IN ('cn','us')
db.Select("address").Group("address").Having("address IN ?", []string{"cn", "us"}).Find(&ps)Distinct
Метод Distinct в основном используется для дедупликации
func (db *DB) Distinct(args ...interface{}) (tx *DB)Смотрите пример
// SELECT DISTINCT `name` FROM `person` WHERE address IN ('cn','us')
db.Where("address IN ?", []string{"cn", "us"}).Distinct("name").Find(&ps)SubQuery
Подзапрос — это вложенный запрос. Например, если вы хотите найти всех людей, у которых значение id больше среднего
// SELECT * FROM `person` WHERE id > (SELECT AVG(id) FROM `person`
db.Where("id > (?)", db.Model(Person{}).Select("AVG(id)")).Find(&ps)Из подзапроса
// SELECT * FROM (SELECT * FROM `person` WHERE address IN ('cn','uk')) as p
db.Table("(?) as p", db.Model(Person{}).Where("address IN ?", []string{"cn", "uk"})).Find(&ps)Lock
Gorm использует clause clause.Locking для поддержки блокировок
// SELECT * FROM `person` FOR UPDATE
db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&ps)
// SELECT * FROM `person` FOR SHARE NOWAIT
db.Clauses(clause.Locking{Strength: "SHARE", Options: "NOWAIT"}).Find(&ps)Iteration
Вы можете получить итератор через метод Rows
func (db *DB) Rows() (*sql.Rows, error)Перебирая итератор, используйте метод ScanRows для сканирования результата каждой строки в struct.
rows, err := db.Model(Person{}).Rows()
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
var p Person
err := db.ScanRows(rows, &p)
if err != nil {
return
}
}Обновление
Save
Метод Save упоминался во время создания. Он также может использоваться для обновления записей, и он обновит все поля, даже если некоторые поля struct имеют нулевые значения. Однако, если первичный ключ не совпадает, он выполнит операцию вставки.
var p Person
db.First(&p)
p.Address = "poland"
// UPDATE `person` SET `name`='json',`address`='poland' WHERE `id` = 2
db.Save(&p)Вы можете видеть, что он добавляет все поля, кроме первичного ключа, в оператор SET.
Update
Поэтому в большинстве случаев рекомендуется использовать метод Update
func (db *DB) Update(column string, value interface{}) (tx *DB)Он в основном используется для обновления одного поля столбца
var p Person
db.First(&p)
// UPDATE `person` SET `address`='poland' WHERE id = 2
db.Model(Person{}).Where("id = ?", p.Id).Update("address", "poland")Updates
Метод Updates используется для обновления нескольких столбцов, принимает struct и map в качестве параметров. Когда поля struct имеют нулевые значения, эти поля будут проигнорированы, но не в map.
func (db *DB) Updates(values interface{}) (tx *DB)Вот пример
var p Person
db.First(&p)
// UPDATE `person` SET `name`='jojo',`address`='poland' WHERE `id` = 2
db.Model(p).Updates(Person{Name: "jojo", Address: "poland"})
// UPDATE `person` SET `address`='poland',`name`='jojo' WHERE `id` = 2
db.Model(p).Updates(map[string]any{"name": "jojo", "address": "poland"})SQL-выражения
Иногда вам часто нужно выполнять операции над полями, такие как самоинкремент или самодекремент или другие операции, включающие само поле. Обычно вы сначала запрашиваете, затем вычисляете, затем обновляете, или используете SQL-выражения.
func Expr(expr string, args ...interface{}) clause.ExprСмотрите следующий пример
// UPDATE `person` SET `age`=age + age,`name`='jojo' WHERE `id` = 2
db.Model(p).Updates(map[string]any{"name": "jojo", "age": gorm.Expr("age + age")})
// UPDATE `person` SET `age`=age * 2 + age,`name`='jojo' WHERE `id` = 2
db.Model(p).Updates(map[string]any{"name": "jojo", "age": gorm.Expr("age * 2 + age")})Удаление
В gorm удаление записей использует метод Delete. Он может напрямую принимать struct сущности или передавать условия.
func (db *DB) Delete(value interface{}, conds ...interface{}) (tx *DB)Например, напрямую передать struct
var p Person
db.First(&p)
// DELETE FROM `person` WHERE `person`.`id` = 2
db.Delete(&p)Или
var p Person
db.First(&p)
// DELETE FROM `person` WHERE `person`.`id` = 2
db.Model(p).Delete(nil)Или указать условия
// DELETE FROM `person` WHERE id = 2
db.Model(Person{}).Where("id = ?", p.Id).Delete(nil)Или упростить до
var p Person
db.First(&p)
// DELETE FROM `person` WHERE id = 2
db.Delete(&Person{}, "id = ?", 2)
// DELETE FROM `person` WHERE `person`.`id` = 2
db.Delete(&Person{}, 2)Для пакетного удаления передайте slice
// DELETE FROM `person` WHERE id IN (1,2,3)
db.Delete(&Person{}, "id IN ?", []uint{1, 2, 3})
// DELETE FROM `person` WHERE `person`.`id` IN (1,2,3)
db.Delete(&Person{}, []uint{1, 2, 3})Мягкое удаление
Если ваша модель сущности использует мягкое удаление, во время удаления по умолчанию выполняется операция обновления. Если вы хотите навсегда удалить, вы можете использовать метод Unscoped
db.Unscoped().Delete(&Person{}, []uint{1, 2, 3})Определение ассоциации
Gorm предоставляет возможности взаимодействия с ассоциациями таблиц, определяя ассоциации между struct через встроенные struct и поля.
Один-к-одному
Отношение один-к-одному самое простое. Обычно у человека может быть только одна мать. Смотрите следующий struct
type Person struct {
Id uint
Name string
Address string
Age uint
MomId sql.NullInt64
Mom Mom `gorm:"foreignKey:MomId;"`
}
type Mom struct {
Id uint
Name string
}Struct Person реализует ссылку на тип Mom путём встраивания struct Mom. Среди них Person.MomId — поле ссылки, а первичный ключ Mom.Id — ссылочное поле. Это завершает ассоциацию один-к-одному. Как настраивать внешние ключи, ссылки, ограничения и правила внешних ключей по умолчанию было объяснено в Определение внешнего ключа, поэтому не будет повторяться.
TIP
Для полей внешних ключей рекомендуется использовать типы, предоставленные пакетом sql, потому что внешние ключи по умолчанию могут быть NULL. При использовании Create для создания записей, если вы используете обычные типы, нулевое значение 0 также будет создано. Создание несуществующего внешнего ключа, очевидно, не разрешено.
Один-ко-многим
Ниже добавим struct school. Отношение между school и student — один-ко-многим. У школы много студентов, но студент может посещать только одну школу.
type Person struct {
Id uint
Name string
Address string
Age uint
MomId sql.NullInt64
Mom Mom `gorm:"foreignKey:MomId;"`
SchoolId sql.NullInt64
School School `gorm:"foreignKey:SchoolId;"`
}
type Mom struct {
Id uint
Name string
}
type School struct {
Id uint
Name string
Persons []Person `gorm:"foreignKey:SchoolId;"`
}school.Persons имеет тип []Person, указывая, что он может иметь несколько студентов, в то время как Person должен содержать внешний ключ, ссылающийся на School, который является Person.SchoolId.
Многие-ко-многим
Человек может владеть многими домами, и в доме могут жить многие люди. Это отношение многие-ко-многим.
type Person struct {
Id uint
Name string
Address string
Age uint
MomId sql.NullInt64
Mom Mom `gorm:"foreignKey:MomId;"`
SchoolId sql.NullInt64
School School `gorm:"foreignKey:SchoolId;"`
Houses []House `gorm:"many2many:person_house;"`
}
type Mom struct {
Id uint
Name string
}
type School struct {
Id uint
Name string
Persons []Person
}
type House struct {
Id uint
Name string
Persons []Person `gorm:"many2many:person_house;"`
}
type PersonHouse struct {
PersonId sql.NullInt64
Person Person `gorm:"foreignKey:PersonId;"`
HouseId sql.NullInt64
House House `gorm:"foreignKey:HouseId;"`
}Person и House держат типы slice друг друга для представления отношения многие-ко-многим. Отношения многие-ко-многим обычно требуют создания таблицы соединения, указанной через many2many. Внешние ключи таблицы соединения должны быть указаны правильно.
После создания struct позвольте gorm автоматически мигрировать в базу данных
tables := []any{
School{},
Mom{},
Person{},
House{},
PersonHouse{},
}
for _, table := range tables {
db.Migrator().CreateTable(&table)
}Обратите внимание на порядок создания между ссылочными и ссылающимися таблицами.
Операции с ассоциациями
После создания вышеуказанных трёх отношений ассоциации, далее следует, как использовать ассоциации для CRUD. В основном используется метод Association
func (db *DB) Association(column string) *AssociationОн принимает параметр ассоциации, значение которого должно быть именем поля ссылочного типа, встроенного в ссылающийся struct.
db.Model(&person).Association("Mom").Find(&mom)Например, чтобы найти мать человека через ассоциацию, параметр Association — Mom, который является именем поля Person.Mom.
Создание ассоциации
// Определить данные
jenny := Mom{
Name: "jenny",
}
mit := School{
Name: "MIT",
Persons: nil,
}
h1 := House{
Id: 0,
Name: "h1",
Persons: nil,
}
h2 := House{
Name: "h2",
Persons: nil,
}
jack := Person{
Name: "jack",
Address: "usa",
Age: 18,
}
mike := Person{
Name: "mike",
Address: "uk",
Age: 20,
}
// INSERT INTO `people` (`name`,`address`,`age`,`mom_id`,`school_id`) VALUES ('jack','usa',18,NULL,NULL)
db.Create(&jack)
// INSERT INTO `schools` (`name`) VALUES ('MIT')
db.Create(&mit)
// Добавить ассоциацию Person-Mom, ассоциация один-к-одному
// INSERT INTO `moms` (`name`) VALUES ('jenny') ON DUPLICATE KEY UPDATE `id`=`id`
// UPDATE `people` SET `mom_id`=1 WHERE `id` = 1
db.Model(&jack).Association("Mom").Append(&jenny)
// Добавить ассоциацию school-Person, ассоциация один-ко-многим
// INSERT INTO `people` (`name`,`address`,`age`,`mom_id`,`school_id`,`id`) VALUES ('jack','usa',18,1,1,1),('mike','uk',20,NULL,1,DEFAULT) ON DUPLICATE KEY UPDATE `school_id`=VALUES(`school_id`)
db.Model(&mit).Association("Persons").Append([]Person{jack, mike})
// Добавить ассоциацию Person-Houses, ассоциация многие-ко-многим
// INSERT INTO `houses` (`name`) VALUES ('h1'),('h2') ON DUPLICATE KEY UPDATE `id`=`id`
// INSERT INTO `person_house` (`person_id`,`house_id`) VALUES (1,1),(1,2) ON DUPLICATE KEY UPDATE `person_id`=`person_id`
db.Model(&jack).Association("Houses").Append([]House{h1, h2})Если все записи не существуют, во время создания ассоциации сначала будут созданы записи, затем создана ассоциация.
Поиск ассоциации
Ниже демонстрируется, как искать ассоциации.
// Поиск ассоциации один-к-одному
var person Person
var mom Mom
// SELECT * FROM `people` ORDER BY `people`.`id` LIMIT 1
db.First(&person)
// SELECT * FROM `moms` WHERE `moms`.`id` = 1
db.Model(person).Association("Mom").Find(&mom)
// Поиск ассоциации один-ко-многим
var school School
var persons []Person
// SELECT * FROM `schools` ORDER BY `schools`.`id` LIMIT 1
db.First(&school)
// SELECT * FROM `people` WHERE `people`.`school_id` = 1
db.Model(&school).Association("Persons").Find(&persons)
// Поиск ассоциации многие-ко-многим
var houses []House
// SELECT `houses`.`id`,`houses`.`name` FROM `houses` JOIN `person_house` ON `person_house`.`house_id` = `houses`.`id` AND `person_house`.`person_id` IN (1,2)
db.Model(&persons).Association("Houses").Find(&houses)Поиск ассоциации найдёт записи, соответствующие условиям в ссылочной таблице, на основе существующих данных. Для отношений многие-ко-многим gorm автоматически завершит процесс соединения таблиц.
Обновление ассоциации
Ниже демонстрируется, как обновлять ассоциации
// Обновление ассоциации один-к-одному
var jack Person
lili := Mom{
Name: "lili",
}
// SELECT * FROM `people` WHERE name = 'jack' ORDER BY `people`.`id` LIMIT 1
db.Where("name = ?", "jack").First(&jack)
// INSERT INTO `moms` (`name`) VALUES ('lili')
db.Create(&lili)
// INSERT INTO `moms` (`name`,`id`) VALUES ('lili',2) ON DUPLICATE KEY UPDATE `id`=`id`
// UPDATE `people` SET `mom_id`=2 WHERE `id` = 1
db.Model(&jack).Association("Mom").Replace(&lili)
// Обновление ассоциации один-ко-многим
var mit School
newPerson := []Person{{Name: "bob"}, {Name: "jojo"}}
// INSERT INTO `people` (`name`,`address`,`age`,`mom_id`,`school_id`) VALUES ('bob','',0,NULL,NULL),('jojo','',0,NULL,NULL)
db.Create(&newPerson)
// SELECT * FROM `schools` WHERE name = 'mit' ORDER BY `schools`.`id` LIMIT 1
db.Where("name = ?", "mit").First(&mit)
// INSERT INTO `people` (`name`,`address`,`age`,`mom_id`,`school_id`,`id`) VALUES ('bob','',0,NULL,1,4),('jojo','',0,NULL,1,5) ON DUPLICATE KEY UPDATE `school_id`=VALUES(`school_id`)
// UPDATE `people` SET `school_id`=NULL WHERE `people`.`id` NOT IN (4,5) AND `people`.`school_id` = 1
db.Model(&mit).Association("Persons").Replace(newPerson)
// Обновление ассоциации многие-ко-многим
// INSERT INTO `houses` (`name`) VALUES ('h3'),('h4'),('h5') ON DUPLICATE KEY UPDATE `id`=`id`
// INSERT INTO `person_house` (`person_id`,`house_id`) VALUES (1,3),(1,4),(1,5) ON DUPLICATE KEY UPDATE `person_id`=`person_id`
// DELETE FROM `person_house` WHERE `person_house`.`person_id` = 1 AND `person_house`.`house_id` NOT IN (3,4,5)
db.Model(&jack).Association("Houses").Replace([]House{{Name: "h3"}, {Name: "h4"}, {Name: "h5"}})Во время обновления ассоциации, если и ссылочные данные, и ссылающиеся данные не существуют, gorm попытается создать их.
Удаление ассоциации
Ниже демонстрируется, как удалять ассоциации
// Удаление ассоциации один-к-одному
var (
jack Person
lili Mom
)
// SELECT * FROM `people` WHERE name = 'jack' ORDER BY `people`.`id` LIMIT 1
db.Where("name = ?", "jack").First(&jack)
// SELECT * FROM `moms` WHERE name = 'lili' ORDER BY `moms`.`id` LIMIT 1
db.Where("name = ?", "lili").First(&lili)
// UPDATE `people` SET `mom_id`=NULL WHERE `people`.`id` = 1 AND `people`.`mom_id` = 2
db.Model(&jack).Association("Mom").Delete(&lili)
// Удаление ассоциации один-ко-многим
var (
mit School
persons []Person
)
// SELECT * FROM `schools` WHERE name = 'mit' ORDER BY `schools`.`id` LIMIT 1
db.Where("name = ?", "mit").First(&mit)
// SELECT * FROM `people` WHERE name IN ('jack','mike')
db.Where("name IN ?", []string{"jack", "mike"}).Find(&persons)
// UPDATE `people` SET `school_id`=NULL WHERE `people`.`school_id` = 1 AND `people`.`id` IN (1,2)
db.Model(&mit).Association("Persons").Delete(&persons)
// Удаление ассоциации многие-ко-многим
var houses []House
// SELECT * FROM `houses` WHERE name IN ('h3','h4')
db.Where("name IN ?", []string{"h3", "h4"}).Find(&houses)
// DELETE FROM `person_house` WHERE `person_house`.`person_id` = 1 AND `person_house`.`house_id` IN (3,4)
db.Model(&jack).Association("Houses").Delete(&houses)Удаление ассоциации удаляет только ссылочное отношение между ними, а не записи сущности. Мы также можем использовать метод Clear для прямой очистки ассоциации
db.Model(&jack).Association("Houses").Clear()Если вы хотите удалить соответствующие записи сущности, вы можете добавить операцию Unscoped после операции Association (не повлияет на many2many)
db.Model(&jack).Association("Houses").Unscoped().Delete(&houses)Для один-ко-многим и многие-ко-многим вы можете использовать операцию Select для удаления записей
var (
mit School
)
db.Where("name = ?", "mit").First(&mit)
db.Select("Persons").Delete(&mit)Предзагрузка
Предзагрузка используется для запроса данных ассоциации. Для сущностей с отношениями ассоциации сначала будут предзагружены связанные ссылочные сущности. Запрос ассоциации, упомянутый ранее, запрашивает отношения ассоциации. Предзагрузка напрямую запрашивает записи сущности, включая все отношения ассоциации. С точки зрения синтаксиса, запрос ассоциации должен сначала запросить указанный []Person, затем запросить связанную []Mom на основе []Person. Предзагрузка, с точки зрения синтаксиса, напрямую запрашивает []Person и также загрузит все отношения ассоциации. Однако на практике SQL, который они выполняют, почти одинаков. Смотрите пример
var users []Person
// SELECT * FROM `moms` WHERE `moms`.`id` = 1
// SELECT * FROM `people`
db.Preload("Mom").Find(&users)Это пример запроса ассоциации один-к-одному. Его вывод
[{Id:1 Name:jack Address:usa Age:18 MomId:{Int64:1 Valid:true} Mom:{Id:1 Name:jenny} SchoolId:{Int64:1 Valid:true} School:{Id:0 Name: Persons:[]} Houses:[]} {Id:2 Name:mike Address:uk Age:20 MomId:{Int64:0 Valid:false} Mom:{Id:0 Name:} SchoolId:{Int64:1 Valid:true} School:{Id:0 Name: Persons:[]} Houses:[]}]Вы можете видеть, что связанная Mom также была запрошена, но отношение school не было предзагружено, поэтому все struct School имеют нулевые значения. Вы также можете использовать clause.Associations для указания предзагрузки всех отношений, кроме вложенных отношений.
db.Preload(clause.Associations).Find(&users)Ниже пример вложенной предзагрузки. Его функция — запросить всех студентов всех ассоциаций school, мать, связанную с каждым студентом, и дома, принадлежащие каждому студенту, а также запросить набор владельцев каждого дома. School->Student->House->Student.
var schools []School
db.Preload("Persons").
Preload("Persons.Mom").
Preload("Persons.Houses").
Preload("Persons.Houses.Persons").Find(&schools)
// Вывод кода, логику можно игнорировать
for _, school := range schools {
fmt.Println("school", school.Name)
for _, person := range school.Persons {
fmt.Println("person", person.Name)
fmt.Println("mom", person.Mom.Name)
for _, house := range person.Houses {
var persons []string
for _, p := range house.Persons {
persons = append(persons, p.Name)
}
fmt.Println("house", house.Name, "owner", persons)
}
fmt.Println()
}
}Вывод
school MIT
person jack
mom jenny
house h1 owner [jack]
house h2 owner [jack]
person mike
momВы можете видеть, что он выводит мать каждого студента каждой школы и их дома, а также владельцев каждого дома.
Транзакции
Gorm включает транзакции по умолчанию. Любые операции вставки и обновления будут откатываться после неудачи. Вы можете отключить это в Конфигурация подключения, производительность улучшится примерно на 30%. Существует несколько методов использования транзакций в gorm. Ниже простое введение.
Автоматические
Транзакция замыкания, через метод Transaction, передайте функцию замыкания. Если возвращаемое значение функции не nil, будет автоматически выполнен откат.
func (db *DB) Transaction(fc func(tx *DB) error, opts ...*sql.TxOptions) (err error)Смотрите пример. Операции в замыкании должны использовать параметр tx, а не внешний db.
var ps []Person
db.Transaction(func(tx *gorm.DB) error {
err := tx.Create(&ps).Error
if err != nil {
return err
}
err = tx.Create(&ps).Error
if err != nil {
return err
}
err = tx.Model(Person{}).Where("id = ?", 1).Update("name", "jack").Error
if err != nil {
return err
}
return nil
})Ручные
Ручные транзакции более рекомендуются, где мы контролируем, когда выполнять откат и когда фиксировать. Ручные транзакции используют следующие три метода
// Метод Begin используется для запуска транзакции
func (db *DB) Begin(opts ...*sql.TxOptions) *DB
// Метод Rollback используется для отката транзакции
func (db *DB) Rollback() *DB
// Метод Commit используется для фиксации транзакции
func (db *DB) Commit() *DBСмотрите пример. После запуска транзакции вы должны использовать tx для работы с ORM.
var ps []Person
tx := db.Begin()
err := tx.Create(&ps).Error
if err != nil {
tx.Rollback()
return
}
err = tx.Create(&ps).Error
if err != nil {
tx.Rollback()
return
}
err = tx.Model(Person{}).Where("id = ?", 1).Update("name", "jack").Error
if err != nil {
tx.Rollback()
return
}
tx.Commit()Вы можете указать точки сохранения
var ps []Person
tx := db.Begin()
err := tx.Create(&ps).Error
if err != nil {
tx.Rollback()
return
}
tx.SavePoint("createBatch")
err = tx.Create(&ps).Error
if err != nil {
tx.Rollback()
return
}
err = tx.Model(Person{}).Where("id = ?", 1).Update("name", "jack").Error
if err != nil {
tx.RollbackTo("createBatch")
return
}
tx.Commit()Заключение
Если вы прочитали всё вышеуказанное содержимое и набрали код, тогда вы можете использовать gorm для выполнения операций CRUD с базой данных. Помимо этих операций, gorm имеет много других функций. Для получения более подробной информации вы можете проверить официальную документацию.
