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
$ go get -u gorm.io/gormConexió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.
$ go get -u gorm.io/driver/mysqlLuego 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
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
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
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.
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.
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.
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.
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.
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
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.
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
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
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.
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.
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
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
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
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
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
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.
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
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
// 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 etiqueta | Descripción |
|---|---|
column | Especificar nombre de columna db |
type | Tipo 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 |
serializer | Especificar el serializador para serializar o deserializar datos a la base de datos, por ejemplo: serializer:json/gob/unixtime |
size | Definir el tamaño o longitud del tipo de dato de columna, por ejemplo size: 256 |
primaryKey | Definir la columna como clave primaria |
unique | Definir la columna como clave única |
default | Definir el valor predeterminado de la columna |
precision | Especificar la precisión de la columna |
scale | Especificar el tamaño de la columna |
not null | Especificar la columna como NOT NULL |
autoIncrement | Especificar la columna como autoincremental |
autoIncrementIncrement | Incremento automático, controla el intervalo entre registros consecutivos |
embedded | Campo incrustado |
embeddedPrefix | Prefijo de nombre de columna para campo incrustado |
autoCreateTime | Rastrear 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 |
autoUpdateTime | Rastrear 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 |
index | Crear í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 |
uniqueIndex | Igual que index, pero crea un índice único |
check | Crear 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 |
comment | Agregar comentario al campo durante la migración |
foreignKey | Especificar la columna del modelo actual como clave externa de la tabla de unión |
references | Especificar el nombre de columna de la tabla referenciada, que se mapeará como clave externa de la tabla de unión |
polymorphic | Especificar el tipo polimórfico, como el nombre del modelo |
polymorphicValue | Especificar el valor polimórfico, valor predeterminado del nombre de tabla |
many2many | Especificar el nombre de la tabla de unión |
joinForeignKey | Especificar el nombre de columna de clave externa de la tabla de unión, que se mapeará a la tabla actual |
joinReferences | Especificar el nombre de columna de clave externa de la tabla de unión, que se mapeará a la tabla referenciada |
constraint | Restricciones 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.
func (db *DB) AutoMigrate(dst ...interface{}) errorPor ejemplo
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
func (db *DB) Migrator() MigratorSoporta los siguientes métodos de interfaz
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
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
func (db *DB) Create(value interface{}) (tx *DB)Existe la siguiente estructura
type Person struct {
Id uint `gorm:"primaryKey;"`
Name string
}Crear un registro
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.RowsAffectedDespué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
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.
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.
func (db *DB) Save(value interface{}) (tx *DB)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
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
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "name"}},
DoNothing: true,
}).Create(&p)O actualizar directamente todos los campos
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
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
var person Person
result := db.First(&person)
err := result.Error
affected := result.RowsAffectedPasar 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.
func (db *DB) Take(dest interface{}, conds ...interface{}) (tx *DB)var person Person
result := db.Take(&person)
err := result.Error
affected := result.RowsAffectedPluck
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.
func (db *DB) Pluck(column string, dest interface{}) (tx *DB)Por ejemplo, recopilar las direcciones de todas las personas en un slice de cadena
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
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
func (db *DB) Count(count *int64) (tx *DB)Ver un ejemplo de uso
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
func (db *DB) Find(dest interface{}, conds ...interface{}) (tx *DB)Buscará todos los registros que coincidan con las condiciones dadas
// 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
func (db *DB) Select(query interface{}, args ...interface{}) (tx *DB)Por ejemplo
// SELECT `address`,`name` FROM `person` ORDER BY `person`.`id` LIMIT 1
db.Select("address", "name").First(&p)Equivalente a
db.Select([]string{"address", "name"}).First(&p)Al mismo tiempo, también se puede usar el método Omit para ignorar campos
func (db *DB) Omit(columns ...string) (tx *DB)Por ejemplo
// 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
func (db *DB) Where(query interface{}, args ...interface{}) (tx *DB)A continuación hay un ejemplo simple
var p Person
db.Where("id = ?", 1).First(&p)Usar múltiples Where en operaciones en cadena construirá múltiples declaraciones AND, por ejemplo
// 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
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)Y el método Not, son similares
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)Para la condición IN, se puede pasar directamente un slice en el método Where.
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
// 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
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
func (db *DB) Order(value interface{}) (tx *DB)Ver un ejemplo de uso
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
// 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
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
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
func (db *DB) Group(name string) (tx *DB)
func (db *DB) Having(query interface{}, args ...interface{}) (tx *DB)Ver un ejemplo a continuación
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
func (db *DB) Distinct(args ...interface{}) (tx *DB)Ver un ejemplo
// 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
// SELECT * FROM `person` WHERE id > (SELECT AVG(id) FROM `person`
db.Where("id > (?)", db.Model(Person{}).Select("AVG(id)")).Find(&ps)subconsulta from
// 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
// 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
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.
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.
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
func (db *DB) Update(column string, value interface{}) (tx *DB)Se usa principalmente para actualizar un solo campo de columna
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.
func (db *DB) Updates(values interface{}) (tx *DB)A continuación hay un ejemplo
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.
func Expr(expr string, args ...interface{}) clause.ExprVer el siguiente ejemplo
// 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.
func (db *DB) Delete(value interface{}, conds ...interface{}) (tx *DB)Por ejemplo, pasar directamente la estructura
var p Person
db.First(&p)
// DELETE FROM `person` WHERE `person`.`id` = 2
db.Delete(&p)O
var p Person
db.First(&p)
// DELETE FROM `person` WHERE `person`.`id` = 2
db.Model(p).Delete(nil)O especificar condiciones
// DELETE FROM `person` WHERE id = 2
db.Model(Person{}).Where("id = ?", p.Id).Delete(nil)También se puede simplificar a
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
// 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
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
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.
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.
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
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
func (db *DB) Association(column string) *AssociationRecibe un parámetro de asociación, su valor debe ser el nombre del campo del tipo referenciado incrustado en la estructura de referencia.
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
// 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.
// 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
// 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
// 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
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)
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
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
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
[{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.
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.
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
momSe 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.
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.
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
// 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() *DBVer un ejemplo a continuación, después de iniciar la transacción, se debe usar tx para operar 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()Se puede especificar un punto de reversión
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.
