Skip to content

Biblioteca ORM Gorm

Documentación oficial: GORM - The fantastic ORM library for Golang, aims to be developer friendly.

Repositorio: go-gorm/gorm: The fantastic ORM library for Golang, aims to be developer friendly (github.com)

En la comunidad de Go, para la interacción con bases de datos, hay dos facciones, una prefiere bibliotecas concisas como sqlx, que no son tan poderosas pero permiten tener control total sobre SQL y optimizar el rendimiento al máximo. La otra facción prefiere ORM que nace para la eficiencia del desarrollo, puede ahorrar muchos problemas innecesarios durante el proceso de desarrollo. Y cuando se habla de ORM, en la comunidad del lenguaje Go es absolutamente imposible evitar gorm, es un ORM muy antiguo, similar a xorm relativamente más joven, ent, etc. Este artículo trata sobre el contenido de gorm, este artículo solo explica su contenido básico de introducción, solo como un punto de partida, si quieres conocer más detalles puedes leer la documentación oficial, su documentación en chino ya está bastante completa, y el autor también es uno de los traductores de la documentación de gorm.

Características

  • ORM de funcionalidad completa
  • Asociación (tiene uno, tiene muchos, pertenece a, muchos a muchos, polimorfismo, herencia de tabla única)
  • Métodos hook en Create, Save, Update, Delete, Find
  • Soporte para Preload, Joins
  • Transacciones, transacciones anidadas, punto de guardado, rollback a punto de guardado
  • Context, modo precompilado, modo DryRun
  • Inserción por lotes, FindInBatches, Find/Create con Map, CRUD usando expresiones SQL, Context Valuer
  • Constructor SQL, Upsert, bloqueo, Optimizer/Index/Comment Hint, parámetros con nombre, subconsulta
  • Clave primaria compuesta, índice, restricción
  • Migración automática
  • Logger personalizado
  • API de plugin flexible y extensible: Database Resolver (múltiples bases de datos, lectura/escritura separadas), Prometheus...
  • Cada característica ha sido probada exhaustivamente
  • Amigable para desarrolladores

gorm también tiene algunas desventajas, por ejemplo, casi todos los parámetros de método son de tipo interfaz vacío, si no lees la documentación probablemente no sabrás qué parámetro pasar, a veces puedes pasar estructura, a veces puedes pasar cadena, a veces puedes pasar map, a veces puedes pasar slice, la semántica es bastante vaga, y en muchas situaciones todavía necesitas escribir SQL manualmente.

Como alternativa hay dos ORM que puedes probar, el primero es aorm, no hace mucho que se abrió, ya no necesitas escribir manualmente los nombres de campos de tabla, en su mayoría es operación en cadena, basado en reflexión, debido al bajo número de estrellas, puedes esperar un poco más. El segundo es ent, es el ORM de código abierto de facebook, también soporta operación en cadena, y en la mayoría de los casos no necesitas escribir SQL manualmente, su filosofía de diseño está basada en grafo (el grafo de estructura de datos), la implementación está basada en generación de código en lugar de reflexión (estoy bastante de acuerdo con esto), pero la documentación está totalmente en inglés, tiene cierta barrera de entrada.

Instalación

Instalar biblioteca gorm

sh
$ go get -u gorm.io/gorm

Conexión

gorm actualmente soporta las siguientes bases de datos

  • 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 es compatible con protocolo mysql
  • ClickHouse: "gorm.io/driver/clickhouse"

Además, hay algunos otros controladores de base de datos proporcionados por desarrolladores de terceros, como el controlador de oracle CengSin/oracle. A continuación se usará MySQL para la demostración, se usa la base de datos que sea, se necesita instalar el controlador correspondiente, aquí se instala el controlador gorm de Mysql.

sh
$ go get -u gorm.io/driver/mysql

Luego conectar a la base de datos usando dsn (data source name), la biblioteca del controlador analizará automáticamente el dsn en la configuración correspondiente

go
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("db connect error", err)
  }
  slog.Info("db connect success")
}

O pasar manualmente la configuración

go
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("db connect error", err)
  }
  slog.Info("db connect success")
}

Ambos métodos son equivalentes, depende del hábito de uso propio.

Configuración de conexión

Al pasar la estructura de configuración gorm.Config, podemos controlar algunos comportamientos de gorm

go
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})

A continuación hay algunas explicaciones simples, se puede configurar según las necesidades propias durante el uso.

go
type Config struct {
  // Deshabilitar transacción predeterminada, gorm iniciará transacción en creación y actualización individual para mantener la consistencia de datos
  SkipDefaultTransaction bool
  // Estrategia de nombramiento personalizada
  NamingStrategy schema.Namer
  // Guardar asociación completa
  FullSaveAssociations bool
  // Logger personalizado
  Logger logger.Interface
  // nowfunc personalizado, para inyectar campos CreatedAt y UpdatedAt
  NowFunc func() time.Time
  // Solo generar sql sin ejecutar
  DryRun bool
  // Usar declaraciones precompiladas
  PrepareStmt bool
  // Después de establecer conexión, hacer ping a la base de datos
  DisableAutomaticPing bool
  // Ignorar clave externa al migrar base de datos
  DisableForeignKeyConstraintWhenMigrating bool
  // Ignorar referencia de asociación al migrar base de datos
  IgnoreRelationshipsWhenMigrating bool
  // Deshabilitar transacciones anidadas
  DisableNestedTransaction bool
  // Ejecutar actualización global, update sin where
  AllowGlobalUpdate bool
  // Consultar todos los campos de la tabla
  QueryFields bool
  // size de creación por lotes
  CreateBatchSize int
  // Habilitar conversión de error
  TranslateError bool

  // ClauseBuilders constructor de cláusulas
  ClauseBuilders map[string]clause.ClauseBuilder
  // ConnPool pool de conexión db
  ConnPool ConnPool
  // Dialector dialector de base de datos
  Dialector
  // Plugins plugins registrados
  Plugins map[string]Plugin

  callbacks  *callbacks
  cacheStore *sync.Map
}

Modelo

En gorm, el modelo corresponde a la tabla de base de datos, generalmente se muestra mediante estructura, como la siguiente estructura.

go
type Person struct {
  Id      uint
  Name    string
  Address string
  Mom     string
  Dad     string
}

El interior de la estructura puede estar compuesto por tipos de datos básicos y tipos que implementan las interfaces sql.Scanner y sql.Valuer. Por defecto, el nombre de tabla mapeado por la estructura Person es persons, que es de estilo plural en snake_case, separado por guión bajo. El nombre de columna también es de estilo snake_case, por ejemplo Id corresponde al nombre de columna id, gorm también proporciona algunas formas de configurarlo.

Especificar nombre de columna

A través de la etiqueta de estructura, podemos especificar el nombre de columna para los campos de estructura, de modo que al mapear entidades, gorm usará el nombre de columna especificado.

go
type Person struct {
  Id      uint   `gorm:"column:ID;"`
  Name    string `gorm:"column:Name;"`
  Address string
  Mom     string
  Dad     string
}

Especificar nombre de tabla

Al implementar la interfaz Table, se puede especificar el nombre de tabla, solo tiene un método, que es devolver el nombre de tabla.

go
type Tabler interface {
  TableName() string
}

En el método implementado, devuelve la cadena person, al migrar la base de datos, gorm creará una tabla llamada person.

go
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"
}

Para la estrategia de nombramiento, también se puede pasar la propia implementación de estrategia al crear la conexión para lograr un efecto personalizado.

Seguimiento de tiempo

go
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"
}

Cuando contiene los campos CreatedAt o UpdatedAt, al crear o actualizar registros, si su valor es cero, gorm usará automáticamente time.Now() para establecer la hora.

go
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 también soporta seguimiento de marca de tiempo

go
type Person struct {
  Id      uint   `gorm:"primaryKey;"`
  Name    string `gorm:"primaryKey;"`
  Address string
  Mom     string
  Dad     string

  // nanoseconds
  CreatedAt uint64 `gorm:"autoCreateTime:nano;"`
  // milliseconds
  UpdatedAt uint64 `gorm:"autoUpdateTime;milli;"`
}

Entonces al ejecutar Create, es equivalente al siguiente SQL

sql
INSERT INTO `person` (`name`,`address`,`mom`,`dad`,`created_at`,`updated_at`) VALUES ('jack','usa','lili','tom',1698216540519000000,1698216540)

En la situación real, si hay necesidad de seguimiento de tiempo, recomiendo almacenar la marca de tiempo en el backend, en el caso de zonas horarias cruzadas, el procesamiento es más simple.

Model

gorm proporciona una estructura Model predefinida, que contiene la clave primaria ID, y dos campos de seguimiento de tiempo, y un campo de registro de eliminación suave.

go
type Model struct {
    ID        uint `gorm:"primarykey"`
    CreatedAt time.Time
    UpdatedAt time.Time
    DeletedAt DeletedAt `gorm:"index"`
}

Al usarlo, solo necesitas incrustarlo en tu modelo de entidad.

go
type Order struct {
  gorm.Model
  Name string
}

De esta manera, tendrá automáticamente todas las características de gorm.Model.

Clave primaria

Por defecto, el campo llamado Id es la clave primaria, se puede usar la etiqueta de estructura para especificar el campo de clave primaria

go
type Person struct {
  Id      uint `gorm:"primaryKey;"`
  Name    string
  Address string
  Mom     string
  Dad     string

  CreatedAt sql.NullTime
  UpdatedAt sql.NullTime
}

Múltiples campos forman una clave primaria compuesta

go
type Person struct {
  Id      uint `gorm:"primaryKey;"`
  Name    string `gorm:"primaryKey;"`
  Address string
  Mom     string
  Dad     string

  CreatedAt sql.NullTime
  UpdatedAt sql.NullTime
}

Índice

Se puede especificar el índice de columna a través de la etiqueta de estructura index

go
type Person struct {
  Id      uint   `gorm:"primaryKey;"`
  Name    string `gorm:"primaryKey;"`
    Address string `gorm:"index:idx_addr,unique,sort:desc;"`
  Mom     string
  Dad     string

  // nanoseconds
  CreatedAt uint64 `gorm:"autoCreateTime:nano;"`
  // milliseconds
  UpdatedAt uint64 `gorm:"autoUpdateTime;milli;"`
}

En la estructura anterior, se estableció un índice único en el campo Address. Dos campos que usan el mismo nombre de índice crearán un índice compuesto

go
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

    // nanoseconds
    CreatedAt uint64 `gorm:"autoCreateTime:nano;"`
    // milliseconds
    UpdatedAt uint64 `gorm:"autoUpdateTime;milli;"`
}

Clave externa

En la estructura, la relación de clave externa se define mediante la incrustación de estructura, por ejemplo

go
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;"`
}

En el ejemplo, la estructura Person tiene dos claves externas, que hacen referencia a las claves primarias de las estructuras Dad y Mom, por defecto se refiere a la clave primaria. Person tiene una relación uno a uno con Dad y Mom, una persona solo puede tener un padre y una madre. Dad y Mom tienen una relación uno a muchos con Person, porque el padre y la madre pueden tener múltiples hijos.

go
Mom   Mom `gorm:"foreignKey:MomId;"`

El efecto de incrustar la estructura es especificar convenientemente la clave externa y la referencia, por defecto el formato del nombre del campo de clave externa es NombreTipoReferenciado+Id, por ejemplo MomId. Por defecto se refiere a la clave primaria, se puede especificar que se haga referencia a un campo determinado a través de la etiqueta de estructura

go
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;"`
}

Donde constraint:OnUpdate:CASCADE,OnDelete:SET NULL; es la restricción de clave externa definida.

Hook

Un modelo de entidad puede personalizar hooks

  • Creación
  • Actualización
  • Eliminación
  • Consulta

Las interfaces correspondientes son las siguientes

go
// Se activa antes de crear
type BeforeCreateInterface interface {
    BeforeCreate(*gorm.DB) error
}

// Se activa después de crear
type AfterCreateInterface interface {
    AfterCreate(*gorm.DB) error
}

// Se activa antes de actualizar
type BeforeUpdateInterface interface {
    BeforeUpdate(*gorm.DB) error
}

// Se activa después de actualizar
type AfterUpdateInterface interface {
    AfterUpdate(*gorm.DB) error
}

// Se activa antes de guardar
type BeforeSaveInterface interface {
    BeforeSave(*gorm.DB) error
}

// Se activa después de guardar
type AfterSaveInterface interface {
    AfterSave(*gorm.DB) error
}

// Se activa antes de eliminar
type BeforeDeleteInterface interface {
    BeforeDelete(*gorm.DB) error
}

// Se activa después de eliminar
type AfterDeleteInterface interface {
    AfterDelete(*gorm.DB) error
}

// Se activa después de consultar
type AfterFindInterface interface {
    AfterFind(*gorm.DB) error
}

La estructura puede personalizar algunos comportamientos implementando estas interfaces.

Etiquetas

A continuación se muestran algunas de las etiquetas soportadas por gorm

Nombre de etiquetaDescripción
columnEspecificar nombre de columna db
typeTipo de dato de columna, se recomienda usar tipos universales con buena compatibilidad, por ejemplo: todas las bases de datos soportan bool, int, uint, float, string, time, bytes y se pueden usar junto con otras etiquetas, como: not null, size, autoIncrement... Especificar tipo de dato de base de datos como varbinary(8) también es soportado. Al usar tipo de dato de base de datos especificado, necesita ser el tipo de dato de base de datos completo, como: MEDIUMINT UNSIGNED not NULL AUTO_INCREMENT
serializerEspecificar el serializador para serializar o deserializar datos a la base de datos, por ejemplo: serializer:json/gob/unixtime
sizeDefinir el tamaño o longitud del tipo de dato de columna, por ejemplo size: 256
primaryKeyDefinir la columna como clave primaria
uniqueDefinir la columna como clave única
defaultDefinir el valor predeterminado de la columna
precisionEspecificar la precisión de la columna
scaleEspecificar el tamaño de la columna
not nullEspecificar la columna como NOT NULL
autoIncrementEspecificar la columna como autoincremental
autoIncrementIncrementIncremento automático, controla el intervalo entre registros consecutivos
embeddedCampo incrustado
embeddedPrefixPrefijo de nombre de columna para campo incrustado
autoCreateTimeRastrear la hora actual al crear, para campos int, rastrea los segundos de la marca de tiempo, puede usar nano/milli para rastrear marcas de tiempo en nanosegundos, milisegundos, por ejemplo: autoCreateTime:nano
autoUpdateTimeRastrear la hora actual al crear/actualizar, para campos int, rastrea los segundos de la marca de tiempo, puede usar nano/milli para rastrear marcas de tiempo en nanosegundos, milisegundos, por ejemplo: autoUpdateTime:milli
indexCrear índice según los parámetros, múltiples campos que usan el mismo nombre crearán un índice compuesto, ver Índices para más detalles
uniqueIndexIgual que index, pero crea un índice único
checkCrear restricción de verificación, por ejemplo check:age > 13, ver Restricciones para más detalles
<-Establecer permisos de escritura del campo, <-:create solo creación, <-:update solo actualización, <-:false sin permisos de escritura, <- permisos de creación y actualización
->Establecer permisos de lectura del campo, ->:false sin permisos de lectura
-Ignorar este campo, - significa sin lectura ni escritura, -:migration significa sin permisos de migración, -:all significa sin permisos de lectura, escritura y migración
commentAgregar comentario al campo durante la migración
foreignKeyEspecificar la columna del modelo actual como clave externa de la tabla de unión
referencesEspecificar el nombre de columna de la tabla referenciada, que se mapeará como clave externa de la tabla de unión
polymorphicEspecificar el tipo polimórfico, como el nombre del modelo
polymorphicValueEspecificar el valor polimórfico, valor predeterminado del nombre de tabla
many2manyEspecificar el nombre de la tabla de unión
joinForeignKeyEspecificar el nombre de columna de clave externa de la tabla de unión, que se mapeará a la tabla actual
joinReferencesEspecificar el nombre de columna de clave externa de la tabla de unión, que se mapeará a la tabla referenciada
constraintRestricciones de relación, por ejemplo: OnUpdate, OnDelete

Migración

El método AutoMigrate nos ayudará a realizar la migración automática, creará tablas, restricciones, índices, claves externas, etc.

go
func (db *DB) AutoMigrate(dst ...interface{}) error

Por ejemplo

go
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`))

O también podemos operarlo manualmente, accediendo a la interfaz Migrator a través del método Migrator

go
func (db *DB) Migrator() Migrator

Soporta los siguientes métodos de interfaz

go
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)
}

La lista de métodos involucra múltiples dimensiones como base de datos, tabla, columna, vista, índice, restricción, para los usuarios que necesitan personalizar, pueden operar de manera más refinada.

Especificar comentario de tabla

Durante la migración, si deseas agregar un comentario de tabla, se puede establecer de la siguiente manera

go
db.Set("gorm:table_options", " comment 'person table'").Migrator().CreateTable(Person{})

Cabe señalar que si se usa el método AutoMigrate() para migrar, y las estructuras tienen una relación de referencia, gorm realizará recursivamente la creación de la tabla referenciada primero, lo que hará que los comentarios de la tabla referenciada y la tabla referenciante sean los mismos, por lo que se recomienda usar el método CreateTable para crear.

TIP

Al crear una tabla, el método CreateTable necesita garantizar que la tabla referenciada se cree antes que la tabla referenciante, de lo contrario se producirá un error, mientras que el método AutoMigrate no lo necesita, porque creará recursivamente siguiendo la relación de referencia.

Creación

Create

Al crear un nuevo registro, en la mayoría de los casos se usa el método Create

go
func (db *DB) Create(value interface{}) (tx *DB)

Existe la siguiente estructura

go
type Person struct {
  Id   uint `gorm:"primaryKey;"`
  Name string
}

Crear un registro

go
user := Person{
    Name: "jack",
}

// Debe pasar una referencia
db = db.Create(&user)

// Error ocurrido durante la ejecución
err = db.Error
// Número de creaciones
affected := db.RowsAffected

Después de la creación, gorm escribirá la clave primaria en la estructura user, por eso es necesario pasar un puntero. Si se pasa un slice, se creará por lotes

go
user := []Person{
    {Name: "jack"},
    {Name: "mike"},
    {Name: "lili"},
}

db = db.Create(&user)

De la misma manera, gorm también escribirá la clave primaria en el slice. Cuando el volumen de datos es demasiado grande, también se puede usar el método CreateInBatches para crear por lotes, porque el SQL generado INSERT INTO table VALUES (),() se volverá muy largo, cada base de datos tiene un límite para la longitud de SQL, por lo que en casos necesarios se puede elegir crear por lotes.

go
db = db.CreateInBatches(&user, 50)

Además, el método Save también puede crear registros, su función es actualizar el registro cuando la clave primaria coincide, de lo contrario insertar.

go
func (db *DB) Save(value interface{}) (tx *DB)
go
user := []Person{
    {Name: "jack"},
    {Name: "mike"},
    {Name: "lili"},
}

db = db.Save(&user)

Upsert

El método Save solo puede coincidir con la clave primaria, podemos construir Clause para completar un upsert más personalizado. Por ejemplo, el siguiente código

go
db.Clauses(clause.OnConflict{
    Columns:   []clause.Column{{Name: "name"}},
    DoNothing: false,
    DoUpdates: clause.AssignmentColumns([]string{"address"}),
    UpdateAll: false,
}).Create(&p)

Su función es cuando el campo name entra en conflicto, actualizar el valor del campo address, si no hay conflicto, se creará un nuevo registro. También se puede hacer nada cuando hay conflicto

go
db.Clauses(clause.OnConflict{
    Columns:   []clause.Column{{Name: "name"}},
    DoNothing: true,
}).Create(&p)

O actualizar directamente todos los campos

go
db.Clauses(clause.OnConflict{
    Columns:   []clause.Column{{Name: "name"}},
    UpdateAll: true,
}).Create(&p)

Antes de usar upsert, recuerda agregar un índice al campo en conflicto.

Consulta

First

gorm proporciona muchos métodos disponibles para consultas, el primero es el método First

go
func (db *DB) First(dest interface{}, conds ...interface{}) (tx *DB)

Su función es buscar el primer registro en orden ascendente de clave primaria, por ejemplo

go
var person Person
result := db.First(&person)
err := result.Error
affected := result.RowsAffected

Pasar el puntero dest para facilitar que gorm mapee los datos consultados a la estructura.

O usar los métodos Table y Model para especificar la tabla de consulta, el primero recibe el nombre de tabla de cadena, el segundo recibe el modelo de entidad.

db.Table("person").Find(&p)
db.Model(Person{}).Find(&p)

TIP

Si el puntero pasado contiene un modelo de entidad, como un puntero de estructura, o un puntero de slice de estructura, entonces no es necesario especificar manualmente qué tabla consultar, esta regla se aplica a todas las operaciones de creación, eliminación, modificación y consulta.

Take

El método Take es similar a First, la diferencia es que no ordenará por clave primaria.

go
func (db *DB) Take(dest interface{}, conds ...interface{}) (tx *DB)
go
var person Person
result := db.Take(&person)
err := result.Error
affected := result.RowsAffected

Pluck

El método Pluck se usa para consultar por lotes una sola columna de una tabla, el resultado de la consulta se puede recopilar en un slice de un tipo especificado, no necesariamente tiene que ser un slice de tipo de entidad.

go
func (db *DB) Pluck(column string, dest interface{}) (tx *DB)

Por ejemplo, recopilar las direcciones de todas las personas en un slice de cadena

go
var adds []string

// SELECT `address` FROM `person` WHERE name IN ('jack','lili')
db.Model(Person{}).Where("name IN ?", []string{"jack", "lili"}).Pluck("address", &adds)

De hecho, es equivalente a

go
db.Select("address").Where("name IN ?", []string{"jack", "lili"}).Find(&adds)

Count

El método Count se usa para contar la cantidad de registros de entidad

go
func (db *DB) Count(count *int64) (tx *DB)

Ver un ejemplo de uso

go
var count int64

// SELECT count(*) FROM `person`
db.Model(Person{}).Count(&count)

Find

El método más común para consultas por lotes es el método Find

go
func (db *DB) Find(dest interface{}, conds ...interface{}) (tx *DB)

Buscará todos los registros que coincidan con las condiciones dadas

go
// SELECT * FROM `person`
var ps []Person
db.Find(&ps)

Select

gorm por defecto consulta todos los campos, podemos usar el método Select para especificar campos

go
func (db *DB) Select(query interface{}, args ...interface{}) (tx *DB)

Por ejemplo

go
// SELECT `address`,`name` FROM `person` ORDER BY `person`.`id` LIMIT 1
db.Select("address", "name").First(&p)

Equivalente a

go
db.Select([]string{"address", "name"}).First(&p)

Al mismo tiempo, también se puede usar el método Omit para ignorar campos

go
func (db *DB) Omit(columns ...string) (tx *DB)

Por ejemplo

go
// 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)

Los campos seleccionados o ignorados por Select y Omit también funcionarán al crear y actualizar consultas.

Where

La consulta de condiciones usará el método Where

go
func (db *DB) Where(query interface{}, args ...interface{}) (tx *DB)

A continuación hay un ejemplo simple

go
var p Person

db.Where("id = ?", 1).First(&p)

Usar múltiples Where en operaciones en cadena construirá múltiples declaraciones AND, por ejemplo

go
// SELECT * FROM `person` WHERE id = 1 AND name = 'jack' ORDER BY `person`.`id` LIMIT 1
db.Where("id = ?", 1).Where("name = ?", "jack").First(&p)

O usar el método Or para construir declaraciones OR

go
func (db *DB) Or(query interface{}, args ...interface{}) (tx *DB)
go
// 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)

Y el método Not, son similares

go
func (db *DB) Not(query interface{}, args ...interface{}) (tx *DB)
go
// 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)

Para la condición IN, se puede pasar directamente un slice en el método Where.

go
db.Where("address IN ?", []string{"cn", "us"}).Find(&ps)

O múltiples condiciones IN de columnas, se necesita usar el tipo [][]any para contener los parámetros

go
// 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 soporta el uso agrupado de where, es decir, combinar las declaraciones anteriores

go
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

El ordenamiento usará el método Order

go
func (db *DB) Order(value interface{}) (tx *DB)

Ver un ejemplo de uso

go
var ps []Person

// SELECT * FROM `person` ORDER BY name ASC, id DESC
db.Order("name ASC, id DESC").Find(&ps)

También se puede llamar múltiples veces

go
// SELECT * FROM `person` ORDER BY name ASC, id DESC,address
db.Order("name ASC, id DESC").Order("address").Find(&ps)

Limit

Los métodos Limit y Offset se usan a menudo para consultas de paginación

go
func (db *DB) Limit(limit int) (tx *DB)

func (db *DB) Offset(offset int) (tx *DB)

A continuación hay un ejemplo simple de paginación

go
var (
    ps   []Person
    page = 2
    size = 10
)

// SELECT * FROM `person` LIMIT 10 OFFSET 10
db.Offset((page - 1) * size).Limit(size).Find(&ps)

Group

Los métodos Group y Having se usan a menudo para operaciones de agrupación

go
func (db *DB) Group(name string) (tx *DB)

func (db *DB) Having(query interface{}, args ...interface{}) (tx *DB)

Ver un ejemplo a continuación

go
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

El método Distinct se usa a menudo para eliminar duplicados

go
func (db *DB) Distinct(args ...interface{}) (tx *DB)

Ver un ejemplo

go
// SELECT DISTINCT `name` FROM `person` WHERE address IN ('cn','us')
db.Where("address IN ?", []string{"cn", "us"}).Distinct("name").Find(&ps)

Subconsulta

La subconsulta es una consulta anidada, por ejemplo, si deseas consultar a todas las personas cuyo valor id es mayor que el promedio

go
// SELECT * FROM `person` WHERE id > (SELECT AVG(id) FROM `person`
db.Where("id > (?)", db.Model(Person{}).Select("AVG(id)")).Find(&ps)

subconsulta from

go
// 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)

Bloqueo

gorm usa la cláusula clause.Locking para proporcionar soporte de bloqueo

go
// 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)

Iteración

Se puede obtener un iterador a través del método Rows

go
func (db *DB) Rows() (*sql.Rows, error)

Al iterar sobre el iterador, se puede usar el método ScanRows para escanear el resultado de cada fila en la estructura.

go
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
    }
}

Modificación

save

Se mencionó el método Save al crear, también se puede usar para actualizar registros, y actualizará todos los campos, incluso si algunos campos de estructura son valores cero, pero si la clave primaria no coincide, realizará una operación de inserción.

go
var p Person

db.First(&p)

p.Address = "poland"
// UPDATE `person` SET `name`='json',`address`='poland' WHERE `id` = 2
db.Save(&p)

Se puede ver que agregó todos los campos excepto la clave primaria a la declaración SET.

update

Por lo tanto, en la mayoría de los casos, se recomienda usar el método Update

go
func (db *DB) Update(column string, value interface{}) (tx *DB)

Se usa principalmente para actualizar un solo campo de columna

go
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

El método Updates se usa para actualizar múltiples columnas, recibe estructura y map como parámetros, y cuando el campo de estructura es valor cero, ignorará ese campo, pero en map no.

go
func (db *DB) Updates(values interface{}) (tx *DB)

A continuación hay un ejemplo

go
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"})

Expresión SQL

A veces, a menudo es necesario realizar algunas operaciones de autoincremento o autodecremento u otras operaciones con uno mismo en el campo, generalmente es consultar primero, luego calcular y luego actualizar, o usar una expresión SQL.

go
func Expr(expr string, args ...interface{}) clause.Expr

Ver el siguiente ejemplo

go
// 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")})

Eliminación

En gorm, eliminar registros usará el método Delete, puede pasar directamente la estructura de entidad, o pasar condiciones.

go
func (db *DB) Delete(value interface{}, conds ...interface{}) (tx *DB)

Por ejemplo, pasar directamente la estructura

go
var p Person

db.First(&p)

// DELETE FROM `person` WHERE `person`.`id` = 2
db.Delete(&p)

O

go
var p Person

db.First(&p)

// DELETE FROM `person` WHERE `person`.`id` = 2
db.Model(p).Delete(nil)

O especificar condiciones

go
// DELETE FROM `person` WHERE id = 2
db.Model(Person{}).Where("id = ?", p.Id).Delete(nil)

También se puede simplificar a

go
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)

Para eliminación por lotes, es pasar un slice

go
// 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})

Eliminación suave

Si tu modelo de entidad usa eliminación suave, al eliminar, por defecto se realiza una operación de actualización, si deseas eliminar permanentemente, puedes usar el método Unscoped

go
db.Unscoped().Delete(&Person{}, []uint{1, 2, 3})

Definición de asociación

gorm proporciona capacidad de interacción de asociación de tablas, definiendo la asociación entre estructuras mediante la incrustación de estructuras y campos.

Uno a uno

La relación uno a uno es la más simple, normalmente una persona solo puede tener una madre, ver la siguiente estructura

go
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
}

La estructura Person logra la referencia al tipo Mom mediante la incrustación de la estructura Mom, donde Person.MomId es el campo de referencia, la clave primaria Mom.Id es el campo referenciado, de esta manera se completa la asociación de relación uno a uno. Cómo personalizar claves externas y referencias y restricciones y las reglas predeterminadas de claves externas ya se han explicado en Definición de clave externa, no se repetirá

TIP

Para el campo de clave externa, se recomienda usar el tipo proporcionado por el paquete sql, porque la clave externa por defecto puede ser NULL, al usar Create para crear registros, si se usa un tipo ordinario, el valor cero 0 también se creará, obviamente no se permite que se cree una clave externa que no existe.

Uno a muchos

A continuación se agrega una estructura de escuela, la relación entre escuela y estudiante es uno a muchos, una escuela tiene múltiples estudiantes, pero un estudiante solo puede estudiar en una escuela.

go
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 es de tipo []Person, lo que significa que puede tener múltiples estudiantes, y Person debe contener la clave externa que hace referencia a School, es decir, Person.SchoolId.

Muchos a muchos

Una persona puede tener muchas casas, y una casa puede ser habitada por muchas personas, esta es una relación muchos a muchos.

go
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 y House se mantienen mutuamente el tipo de slice del otro para representar la relación muchos a muchos, la relación muchos a muchos generalmente requiere crear una tabla de unión, se especifica la tabla de unión a través de many2many, la clave externa de la tabla de unión debe especificarse correctamente.

Después de crear la estructura, deja que gorm migre automáticamente a la base de datos

go
tables := []any{
    School{},
    Mom{},
    Person{},
    House{},
    PersonHouse{},
}
for _, table := range tables {
    db.Migrator().CreateTable(&table)
}

Presta atención al orden de creación entre la tabla referenciada y la tabla referenciante.

Operaciones de asociación

Después de crear las tres relaciones de asociación anteriores, el siguiente paso es cómo usar la asociación para crear, eliminar, modificar y consultar. Esto usará principalmente el método Association

go
func (db *DB) Association(column string) *Association

Recibe un parámetro de asociación, su valor debe ser el nombre del campo del tipo referenciado incrustado en la estructura de referencia.

go
db.Model(&person).Association("Mom").Find(&mom)

Por ejemplo, al buscar la madre de una persona mediante asociación, el parámetro de Association es Mom, es decir, el nombre del campo Person.Mom.

Crear asociación

go
// Definir datos
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)

// Agregar asociación entre Person y Mom, asociación uno a uno
// 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)

// Agregar asociación entre school y Person, asociación uno a muchos
// 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})

// Agregar asociación entre Person y Houses, asociación muchos a muchos
// 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})

Si todos los registros no existen, al crear una asociación, también se crearán los registros primero y luego se creará la asociación.

Buscar asociación

A continuación se demuestra cómo buscar una asociación.

go
// Búsqueda de asociación uno a uno
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)

// Búsqueda de asociación uno a muchos
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)

// Búsqueda de asociación muchos a muchos
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)

La búsqueda de asociación buscará registros que cumplan con las condiciones en la tabla de referencia según los datos existentes, para la relación muchos a muchos, gorm completará automáticamente el proceso de unión de tablas.

Actualizar asociación

A continuación se demuestra cómo actualizar una asociación

go
// Actualización de asociación uno a uno
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)

// Actualización de asociación uno a muchos

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)

// Actualización de asociación muchos a muchos

// 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"}})

Al actualizar una asociación, si los datos referenciados y los datos de referencia no existen, gorm intentará crearlos.

Eliminar asociación

A continuación se demuestra cómo eliminar una asociación

go
// Eliminación de asociación uno a uno
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)

// Eliminación de asociación uno a muchos

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)

// Eliminación de asociación muchos a muchos
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)

Al eliminar una asociación, solo se eliminará la relación de referencia entre ellos, no se eliminarán los registros de entidad. También podemos usar el método Clear para limpiar directamente la asociación

go
db.Model(&jack).Association("Houses").Clear()

Si deseas eliminar los registros de entidad correspondientes, puedes agregar la operación Unscoped después de la operación Association (no afectará many2many)

go
db.Model(&jack).Association("Houses").Unscoped().Delete(&houses)

Para uno a muchos y muchos a muchos, se puede usar la operación Select para eliminar registros

go
var (
    mit     School
)
db.Where("name = ?", "mit").First(&mit)

db.Select("Persons").Delete(&mit)

Preload

Preload se usa para consultar datos de asociación, para entidades con relaciones de asociación, cargará primero la entidad referenciada por la asociación. La consulta de asociación mencionada anteriormente es para consultar la relación de asociación, preload es consultar directamente los registros de entidad, incluyendo todas las relaciones de asociación. Desde la perspectiva de la sintaxis, la consulta de asociación necesita consultar primero []Person especificado, y luego consultar []Mom asociado según []Person, preload consulta directamente []Person desde la sintaxis, y también cargará todas las relaciones de asociación, pero en realidad el SQL que ejecutan es casi el mismo. Ver un ejemplo a continuación

go
var users []Person

// SELECT * FROM `moms` WHERE `moms`.`id` = 1
// SELECT * FROM `people`
db.Preload("Mom").Find(&users)

Este es un ejemplo de consulta de asociación uno a uno, su salida

go
[{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:[]}]

Se puede ver que se consultó el Mom asociado, pero no se precargó la relación de escuela, todos los School son valores cero. También se puede usar clause.Associations para indicar que se precarguen todas las relaciones, excepto las relaciones anidadas.

go
db.Preload(clause.Associations).Find(&users)

A continuación se ve un ejemplo de precarga anidada, su función es consultar todos los estudiantes de todas las asociaciones de escuelas y la madre asociada con cada estudiante y las casas poseídas por cada estudiante, y también consultar el conjunto de propietarios de cada casa, escuela->estudiante->casa->estudiante.

go
var schools []School

db.Preload("Persons").
    Preload("Persons.Mom").
    Preload("Persons.Houses").
    Preload("Persons.Houses.Persons").Find(&schools)

// Código de salida, la lógica se puede ignorar
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()
    }
}

La salida es

school MIT
person jack
mom jenny
house h1 owner [jack]
house h2 owner [jack]

person mike
mom

Se puede ver que se outputó la madre de cada estudiante de cada escuela y sus casas, y todos los propietarios de las casas.

Transacciones

gorm habilita transacciones por defecto, cualquier inserción y actualización fallida se revertirá, se puede desactivar en Configuración de conexión, el rendimiento mejorará aproximadamente un 30%. Hay varios métodos para usar transacciones en gorm, a continuación se presenta una introducción simple.

Automático

Transacción de cierre, a través del método Transaction, pasando una función de cierre, si el valor de retorno de la función no es nil, se revertirá automáticamente.

go
func (db *DB) Transaction(fc func(tx *DB) error, opts ...*sql.TxOptions) (err error)

Ver un ejemplo a continuación, las operaciones en el cierre deben usar el parámetro tx, en lugar del db externo.

go
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
})

Manual

Se recomienda usar transacciones manuales, nosotros mismos controlamos cuándo revertir y cuándo confirmar. Las transacciones manuales usarán los siguientes tres métodos

go
// El método Begin se usa para iniciar una transacción
func (db *DB) Begin(opts ...*sql.TxOptions) *DB

// El método Rollback se usa para revertir una transacción
func (db *DB) Rollback() *DB

// El método Commit se usa para confirmar una transacción
func (db *DB) Commit() *DB

Ver un ejemplo a continuación, después de iniciar la transacción, se debe usar tx para operar orm.

go
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()

Se puede especificar un punto de reversión

go
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()

Resumen

Si has leído todo el contenido anterior y has escrito el código, entonces puedes usar gorm para realizar operaciones de creación, eliminación, modificación y consulta en la base de datos, además de estas operaciones, gorm tiene muchas otras funciones, se pueden conocer más detalles en la documentación oficial.

Golang editado por www.golangdev.cn