Gorm Bibliothèque ORM de Base de Données
Documentation officielle : GORM - The fantastic ORM library for Golang, aims to be developer friendly.
Dépôt open source : go-gorm/gorm: The fantastic ORM library for Golang, aims to be developer friendly (github.com)
Dans la communauté Go, en ce qui concerne l'interaction avec les bases de données, il y a deux camps. Un camp préfère des bibliothèques plus simples comme sqlx, qui sont moins puissantes mais permettent de contrôler le SQL à tout moment et d'optimiser les performances à l'extrême. L'autre camp aime les ORM conçus pour l'efficacité du développement, qui permettent d'éviter de nombreux problèmes inutiles lors du processus de développement. En ce qui concerne les ORM, dans la communauté Go, il est impossible de ne pas mentionner gorm, c'est un ORM très établi. Il existe également des alternatives plus récentes comme xorm, ent, etc. Cet article traite de gorm et présente uniquement son contenu de base pour les débutants, servant d'introduction. Pour comprendre des détails plus approfondis, vous pouvez consulter la documentation officielle, dont la documentation chinoise est déjà très complète.
Caractéristiques
- ORM complet
- Associations (has one, has many, belongs to, many-to-many, polymorphique, single table inheritance)
- Méthodes hooks dans Create, Save, Update, Delete, Find
- Préchargement supporté via Preload, Joins
- Transactions, transactions imbriquées, Save Point, Rollback To to Saved Point
- Context, mode précompilé, mode DryRun
- Insertion par lots, FindInBatches, Find/Create with Map, CRUD avec expressions SQL, Context Valuer
- Constructeur SQL, Upsert, verrous, Optimizer/Index/Comment Hint, paramètres nommés, sous-requêtes
- Clés primaires composites, index, contraintes
- Migration automatique
- Logger personnalisé
- API de plugins extensible : Database Resolver (multi-bases de données, séparation lecture/écriture), Prometheus…
- Chaque fonctionnalité a été testée rigoureusement
- Convivial pour les développeurs
Bien sûr, gorm a aussi quelques inconvénients. Par exemple, presque tous les paramètres de méthodes sont de type interface vide, ce qui rend difficile de savoir quels paramètres passer sans consulter la documentation. Parfois on peut passer une structure, parfois une chaîne, parfois une map, parfois un slice, la sémantique est assez floue, et dans de nombreux cas, il faut encore écrire le SQL manuellement.
Comme alternatives, deux ORM peuvent être testés. Le premier est aorm, récemment open source, qui ne nécessite plus d'écrire manuellement les noms des champs de table. Dans la plupart des cas, il utilise des opérations chaînées, implémenté par réflexion. Comme il n'a pas beaucoup d'étoiles, on peut attendre encore un peu. Le second est ent, un ORM open source par facebook, qui supporte également les opérations chaînées et dans la plupart des cas ne nécessite pas d'écrire le SQL manuellement. Sa philosophie de conception est basée sur les graphes (le graphe dans les structures de données), et son implémentation est basée sur la génération de code plutôt que sur la réflexion. Cependant, sa documentation est entièrement en anglais, ce qui représente une certaine barrière à l'entrée.
Installation
Installer la bibliothèque gorm
$ go get -u gorm.io/gormConnexion
gorm prend actuellement en charge les bases de données suivantes
- 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 est compatible avec le protocole MySQL - ClickHouse :
"gorm.io/driver/clickhouse"
En outre, certains pilotes de base de données sont fournis par des développeurs tiers, comme le pilote Oracle CengSin/oracle. Cet article utilisera MySQL pour les démonstrations. Selon la base de données utilisée, vous devez installer le pilote correspondant. Ici, nous installons le pilote gorm pour MySQL.
$ go get -u gorm.io/driver/mysqlEnsuite, utilisez le DSN (Data Source Name) pour vous connecter à la base de données. La bibliothèque de pilotes analysera automatiquement le DSN en configuration correspondante.
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")
}Ou passer la configuration manuellement
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")
}Les deux méthodes sont équivalentes, selon vos préférences.
Configuration de Connexion
En passant une structure de configuration gorm.Config, nous pouvons contrôler certains comportements de gorm
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})Voici quelques explications simples, vous pouvez configurer selon vos besoins.
type Config struct {
// Désactiver les transactions par défaut, gorm active les transactions pour chaque création et mise à jour unique pour maintenir la cohérence des données
SkipDefaultTransaction bool
// Stratégorie de nommage personnalisée
NamingStrategy schema.Namer
// Sauvegarder les associations complètes
FullSaveAssociations bool
// Logger personnalisé
Logger logger.Interface
// Fonction now personnalisée, utilisée pour injecter les champs CreatedAt et UpdatedAt
NowFunc func() time.Time
// Générer uniquement le SQL sans l'exécuter
DryRun bool
// Utiliser des instructions préparées
PrepareStmt bool
// Ping la base de données après l'établissement de la connexion
DisableAutomaticPing bool
// Ignorer les clés étrangères lors de la migration de la base de données
DisableForeignKeyConstraintWhenMigrating bool
// Ignorer les références d'association lors de la migration de la base de données
IgnoreRelationshipsWhenMigrating bool
// Désactiver les transactions imbriquées
DisableNestedTransaction bool
// Permettre les mises à jour globales, c'est-à-dire UPDATE sans WHERE
AllowGlobalUpdate bool
// Interroger tous les champs de la table
QueryFields bool
// Taille de création par lots
CreateBatchSize int
// Activer la conversion d'erreurs
TranslateError bool
// ClauseBuilders clause builder
ClauseBuilders map[string]clause.ClauseBuilder
// ConnPool db conn pool
ConnPool ConnPool
// Dialector database dialector
Dialector
// Plugins registered plugins
Plugins map[string]Plugin
callbacks *callbacks
cacheStore *sync.Map
}Modèle
Dans gorm, le modèle correspond à une table de base de données. Il est généralement représenté sous forme de structure, comme dans l'exemple ci-dessous.
type Person struct {
Id uint
Name string
Address string
Mom string
Dad string
}L'intérieur d'une structure peut être composé de types de données de base et de types implémentant les interfaces sql.Scanner et sql.Valuer. Par défaut, le nom de table mappé par la structure Person est persons, au style snake_case pluriel, séparé par des underscores. Les noms de colonnes suivent également le style snake_case, par exemple Id correspond au nom de colonne id. Gorm fournit également plusieurs façons de configurer cela.
Spécifier le Nom de Colonne
En utilisant des tags de structure, nous pouvons spécifier le nom de colonne pour les champs de la structure. Ainsi, lors du mappage d'entité, gorm utilisera le nom de colonne spécifié.
type Person struct {
Id uint `gorm:"column:ID;"`
Name string `gorm:"column:Name;"`
Address string
Mom string
Dad string
}Spécifier le Nom de Table
En implémentant l'interface Table, vous pouvez spécifier le nom de la table. Elle n'a qu'une seule méthode qui retourne le nom de la table.
type Tabler interface {
TableName() string
}Dans la méthode implémentée, elle retourne la chaîne person, lors de la migration de la base de données, gorm créera une table nommée 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"
}Pour la stratégie de nommage, vous pouvez également passer votre propre implémentation de stratégie lors de la création de la connexion pour obtenir un effet personnalisé.
Suivi du Temps
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"
}Lorsque les champs CreatedAt ou UpdatedAt sont présents, lors de la création ou de la mise à jour d'enregistrements, s'ils ont une valeur zéro, gorm utilisera automatiquement time.Now() pour définir l'heure.
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 supporte également le suivi par timestamp
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;"`
}Ainsi, lors de l'exécution de Create, cela équivaut au SQL suivant
INSERT INTO `person` (`name`,`address`,`mom`,`dad`,`created_at`,`updated_at`) VALUES ('jack','usa','lili','tom',1698216540519000000,1698216540)En pratique, si vous avez besoin de suivi du temps, je recommande de stocker des timestamps côté backend, ce qui simplifie le traitement dans les situations inter-zones horaires.
Model
Gorm fournit une structure Model prédéfinie qui contient une clé primaire ID, ainsi que deux champs de suivi du temps et un champ d'enregistrement de suppression douce.
type Model struct {
ID uint `gorm:"primarykey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt DeletedAt `gorm:"index"`
}Pour l'utiliser, il suffit de l'intégrer dans votre modèle d'entité.
type Order struct {
gorm.Model
Name string
}Ainsi, elle aura automatiquement toutes les caractéristiques de gorm.Model.
Clé Primaire
Par défaut, le champ nommé Id est la clé primaire. Vous pouvez spécifier le champ de clé primaire en utilisant des tags de structure
type Person struct {
Id uint `gorm:"primaryKey;"`
Name string
Address string
Mom string
Dad string
CreatedAt sql.NullTime
UpdatedAt sql.NullTime
}Plusieurs champs peuvent former une clé primaire composée
type Person struct {
Id uint `gorm:"primaryKey;"`
Name string `gorm:"primaryKey;"`
Address string
Mom string
Dad string
CreatedAt sql.NullTime
UpdatedAt sql.NullTime
}Index
Vous pouvez spécifier un index de colonne via le tag de structure 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;"`
}Dans la structure ci-dessus, un index unique a été créé sur le champ Address. Deux champs utilisant le même nom d'index créeront un index composite
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;"`
}Clé Étrangère
La définition de relations de clé étrangère dans une structure se fait en intégrant des structures, par exemple
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;"`
}Dans l'exemple, la structure Person a deux clés étrangères, référençant respectivement les clés primaires des structures Dad et Mom. Par défaut, la référence est la clé primaire. Person a une relation un-à-un avec Dad et Mom, une personne ne peut avoir qu'un seul père et une seule mère. Dad et Mom ont une relation un-à-plusieurs avec Person, car un père et une mère peuvent avoir plusieurs enfants.
Mom Mom `gorm:"foreignKey:MomId;"`L'intégration de structures sert à faciliter la spécification des clés étrangères et des références. Par défaut, le format du nom de champ de clé étrangère est NomTypeRéférencé+Id, par exemple MomId. Par défaut, la clé primaire est référencée, mais vous pouvez spécifier un champ particulier via les tags de structure
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;"`
}Ici, constraint:OnUpdate:CASCADE,OnDelete:SET NULL; définit la contrainte de clé étrangère.
Hooks
Un modèle d'entité peut définir des hooks personnalisés
- Création
- Mise à jour
- Suppression
- Requête
Les interfaces correspondantes sont les suivantes
// Déclenché avant la création
type BeforeCreateInterface interface {
BeforeCreate(*gorm.DB) error
}
// Déclenché après la création
type AfterCreateInterface interface {
AfterCreate(*gorm.DB) error
}
// Déclenché avant la mise à jour
type BeforeUpdateInterface interface {
BeforeUpdate(*gorm.DB) error
}
// Déclenché après la mise à jour
type AfterUpdateInterface interface {
AfterUpdate(*gorm.DB) error
}
// Déclenché avant la sauvegarde
type BeforeSaveInterface interface {
BeforeSave(*gorm.DB) error
}
// Déclenché après la sauvegarde
type AfterSaveInterface interface {
AfterSave(*gorm.DB) error
}
// Déclenché avant la suppression
type BeforeDeleteInterface interface {
BeforeDelete(*gorm.DB) error
}
// Déclenché après la suppression
type AfterDeleteInterface interface {
AfterDelete(*gorm.DB) error
}
// Déclenché après la requête
type AfterFindInterface interface {
AfterFind(*gorm.DB) error
}En implémentant ces interfaces, les structures peuvent personnaliser certains comportements.
Tags
Voici quelques tags supportés par gorm
| Nom du tag | Description |
|---|---|
column | Spécifie le nom de colonne db |
type | Type de données de colonne, il est recommandé d'utiliser des types génériques avec une bonne compatibilité, par exemple : toutes les bases de données supportent bool, int, uint, float, string, time, bytes et peuvent être utilisés avec d'autres tags, par exemple : not null, size, autoIncrement… Spécifier un type de données de base de données comme varbinary(8) est également supporté. Lors de l'utilisation d'un type de données de base de données spécifique, il doit être complet, comme : MEDIUMINT UNSIGNED not NULL AUTO_INCREMENT |
serializer | Spécifie le sérialiseur pour sérialiser ou désérialiser les données dans la base de données, par exemple : serializer:json/gob/unixtime |
size | Définit la taille ou la longueur du type de données de colonne, par exemple size: 256 |
primaryKey | Définit la colonne comme clé primaire |
unique | Définit la colonne comme clé unique |
default | Définit la valeur par défaut de la colonne |
precision | Spécifie la précision de la colonne |
scale | Spécifie l'échelle de la colonne |
not null | Spécifie la colonne comme NOT NULL |
autoIncrement | Spécifie la colonne comme auto-incrémentée |
autoIncrementIncrement | Pas d'auto-incrémentation, contrôle l'intervalle entre les enregistrements consécutifs |
embedded | Champ imbriqué |
embeddedPrefix | Préfixe du nom de colonne pour les champs imbriqués |
autoCreateTime | Suit l'heure actuelle lors de la création, pour les champs int, il suit les secondes timestamp, vous pouvez utiliser nano/milli pour suivre les timestamps en nanosecondes, millisecondes, par exemple : autoCreateTime:nano |
autoUpdateTime | Suit l'heure actuelle lors de la création/mise à jour, pour les champs int, il suit les secondes timestamp, vous pouvez utiliser nano/milli pour suivre les timestamps en nanosecondes, millisecondes, par exemple : autoUpdateTime:milli |
index | Crée un index selon les paramètres, plusieurs champs utilisant le même nom créent un index composite, voir Index open in new window pour plus de détails |
uniqueIndex | Identique à index, mais crée un index unique |
check | Crée une contrainte de vérification, par exemple check:age > 13, voir Contraintes open in new window pour plus de détails |
<- | Définit les permissions d'écriture du champ, <-:create création uniquement, <-:update mise à jour uniquement, <-:false aucune permission d'écriture, <- permission de création et mise à jour |
-> | Définit les permissions de lecture du champ, ->:false aucune permission de lecture |
- | Ignore ce champ, - signifie pas de lecture/écriture, -:migration signifie pas de permission de migration, -:all signifie pas de permission de lecture/écriture/migration |
comment | Ajoute un commentaire au champ lors de la migration |
foreignKey | Spécifie la colonne du modèle actuel comme clé étrangère de la table de jointure |
references | Spécifie le nom de colonne de la table référencée, qui sera mappé comme clé étrangère de la table de jointure |
polymorphic | Spécifie le type polymorphique, comme le nom du modèle |
polymorphicValue | Spécifie la valeur polymorphique, nom de table par défaut |
many2many | Spécifie le nom de la table de jointure |
joinForeignKey | Spécifie le nom de colonne de clé étrangère dans la table de jointure, qui sera mappé à la table actuelle |
joinReferences | Spécifie le nom de colonne de clé étrangère dans la table de jointure, qui sera mappé à la table référencée |
constraint | Contrainte de relation, par exemple : OnUpdate, OnDelete |
Migration
La méthode AutoMigrate nous aide à effectuer une migration automatique, elle créera des tables, des contraintes, des index, des clés étrangères, etc.
func (db *DB) AutoMigrate(dst ...interface{}) errorPar exemple
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`))Ou vous pouvez également effectuer des opérations manuelles via la méthode Migrator pour accéder à l'interface Migrator
func (db *DB) Migrator() MigratorElle supporte les méthodes d'interface suivantes
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 liste des méthodes couvre plusieurs dimensions : base de données, tables, colonnes, vues, index, contraintes, permettant aux utilisateurs ayant besoin de personnalisation d'effectuer des opérations plus précises.
Spécifier un Commentaire de Table
Lors de la migration, si vous souhaitez ajouter un commentaire de table, vous pouvez le configurer comme suit
db.Set("gorm:table_options", " comment 'person table'").Migrator().CreateTable(Person{})Notez que si vous utilisez la méthode AutoMigrate() pour la migration et qu'il existe des relations de référence entre les structures, gorm créera récursivement les tables référencées en premier, ce qui entraînera des commentaires dupliqués pour les tables référencées et les tables de référence. Il est donc recommandé d'utiliser la méthode CreateTable pour créer.
TIP
Lors de la création de tables avec la méthode CreateTable, vous devez vous assurer que la table référencée est créée avant la table de référence, sinon une erreur se produira. La méthode AutoMigrate n'a pas cette exigence car elle crée récursivement selon les relations de référence.
Création
Create
Lors de la création de nouveaux enregistrements, la méthode Create est utilisée dans la plupart des cas
func (db *DB) Create(value interface{}) (tx *DB)Soit la structure suivante
type Person struct {
Id uint `gorm:"primaryKey;"`
Name string
}Créer un enregistrement
user := Person{
Name: "jack",
}
// Doit passer une référence
db = db.Create(&user)
// Erreur survenue pendant l'exécution
err = db.Error
// Nombre d'enregistrements créés
affected := db.RowsAffectedAprès la création, gorm écrira la clé primaire dans la structure user, c'est pourquoi il est nécessaire de passer un pointeur. Si vous passez un slice, cela créera plusieurs enregistrements en lot
user := []Person{
{Name: "jack"},
{Name: "mike"},
{Name: "lili"},
}
db = db.Create(&user)De même, gorm écrira les clés primaires dans le slice. Lorsque la quantité de données est importante, vous pouvez utiliser la méthode CreateInBatches pour créer par lots, car l'instruction SQL générée INSERT INTO table VALUES (),() peut devenir très longue, et chaque base de données a une limite sur la longueur du SQL.
db = db.CreateInBatches(&user, 50)En outre, la méthode Save peut également créer des enregistrements. Son rôle est de mettre à jour l'enregistrement si la clé primaire correspond, sinon de l'insérer.
func (db *DB) Save(value interface{}) (tx *DB)user := []Person{
{Name: "jack"},
{Name: "mike"},
{Name: "lili"},
}
db = db.Save(&user)Upsert
La méthode Save ne peut correspondre qu'à la clé primaire. Nous pouvons construire une Clause pour un upsert plus personnalisé. Par exemple, le code suivant
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "name"}},
DoNothing: false,
DoUpdates: clause.AssignmentColumns([]string{"address"}),
UpdateAll: false,
}).Create(&p)Son rôle est de mettre à jour la valeur du champ address lorsqu'il y a un conflit sur le champ name, sinon de créer un nouvel enregistrement. Vous pouvez également ne rien faire en cas de conflit
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "name"}},
DoNothing: true,
}).Create(&p)Ou mettre à jour tous les champs directement
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "name"}},
UpdateAll: true,
}).Create(&p)Avant d'utiliser upsert, n'oubliez pas d'ajouter un index au champ de conflit.
Requête
First
Pour les requêtes, gorm fournit de nombreuses méthodes. La première est la méthode First
func (db *DB) First(dest interface{}, conds ...interface{}) (tx *DB)Son rôle est de trouver le premier enregistrement trié par clé primaire croissante, par exemple
var person Person
result := db.First(&person)
err := result.Error
affected := result.RowsAffectedPasser un pointeur dest permet à gorm de mapper les données interrogées dans la structure.
Ou utiliser les méthodes Table et Model pour spécifier la table à interroger, la première reçoit un nom de table sous forme de chaîne, la seconde reçoit un modèle d'entité.
db.Table("person").Find(&p)
db.Model(Person{}).Find(&p)TIP
Si l'élément pointé passé contient un modèle d'entité comme un pointeur de structure, ou un pointeur de slice de structures, alors il n'est pas nécessaire de spécifier manuellement la table à interroger. Cette règle s'applique à toutes les opérations CRUD.
Take
La méthode Take est similaire à First, la différence est qu'elle ne trie pas par clé primaire.
func (db *DB) Take(dest interface{}, conds ...interface{}) (tx *DB)var person Person
result := db.Take(&person)
err := result.Error
affected := result.RowsAffectedPluck
La méthode Pluck est utilisée pour interroger en lot une seule colonne d'une table. Les résultats peuvent être collectés dans un slice d'un type spécifié, pas nécessairement un slice de type entité.
func (db *DB) Pluck(column string, dest interface{}) (tx *DB)Par exemple, collecter toutes les adresses des personnes dans un slice de chaînes
var adds []string
// SELECT `address` FROM `person` WHERE name IN ('jack','lili')
db.Model(Person{}).Where("name IN ?", []string{"jack", "lili"}).Pluck("address", &adds)C'est en fait équivalent à
db.Select("address").Where("name IN ?", []string{"jack", "lili"}).Find(&adds)Count
La méthode Count est utilisée pour compter le nombre d'enregistrements d'entité
func (db *DB) Count(count *int64) (tx *DB)Voici un exemple d'utilisation
var count int64
// SELECT count(*) FROM `person`
db.Model(Person{}).Count(&count)Find
La méthode la plus couramment utilisée pour les requêtes par lot est Find
func (db *DB) Find(dest interface{}, conds ...interface{}) (tx *DB)Elle trouve tous les enregistrements correspondant aux conditions données
// SELECT * FROM `person`
var ps []Person
db.Find(&ps)Select
Par défaut, gorm interroge tous les champs. Nous pouvons spécifier les champs via la méthode Select
func (db *DB) Select(query interface{}, args ...interface{}) (tx *DB)Par exemple
// SELECT `address`,`name` FROM `person` ORDER BY `person`.`id` LIMIT 1
db.Select("address", "name").First(&p)Équivalent à
db.Select([]string{"address", "name"}).First(&p)De plus, vous pouvez utiliser la méthode Omit pour ignorer des champs
func (db *DB) Omit(columns ...string) (tx *DB)Par exemple
// 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)``Les champs sélectionnés ou ignorés par Select et Omit seront effectifs lors de la création, mise à jour et requête.
Where
La méthode Where est utilisée pour les requêtes conditionnelles
func (db *DB) Where(query interface{}, args ...interface{}) (tx *DB)Voici un exemple simple
var p Person
db.Where("id = ?", 1).First(&p)L'utilisation de plusieurs Where dans une opération chaînée construira plusieurs instructions AND, par exemple
// SELECT * FROM `person` WHERE id = 1 AND name = 'jack' ORDER BY `person`.`id` LIMIT 1
db.Where("id = ?", 1).Where("name = ?", "jack").First(&p)Ou utiliser la méthode Or pour construire des instructions 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)Il y a aussi la méthode Not, qui est similaire
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)Pour les conditions IN, vous pouvez directement passer un slice dans la méthode Where.
db.Where("address IN ?", []string{"cn", "us"}).Find(&ps)Ou pour les conditions IN multi-colonnes, utilisez le type [][]any pour contenir les paramètres
// 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 supporte le groupement de where, c'est-à-dire combiner les instructions ci-dessus
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
Le tri utilise la méthode Order
func (db *DB) Order(value interface{}) (tx *DB)Voici un exemple d'utilisation
var ps []Person
// SELECT * FROM `person` ORDER BY name ASC, id DESC
db.Order("name ASC, id DESC").Find(&ps)Vous pouvez également l'appeler plusieurs fois
// SELECT * FROM `person` ORDER BY name ASC, id DESC,address
db.Order("name ASC, id DESC").Order("address").Find(&ps)Limit
Les méthodes Limit et Offset sont souvent utilisées pour la pagination
func (db *DB) Limit(limit int) (tx *DB)
func (db *DB) Offset(offset int) (tx *DB)Voici un exemple simple de pagination
var (
ps []Person
page = 2
size = 10
)
// SELECT * FROM `person` LIMIT 10 OFFSET 10
db.Offset((page - 1) * size).Limit(size).Find(&ps)Group
Les méthodes Group et Having sont principalement utilisées pour les opérations de groupement
func (db *DB) Group(name string) (tx *DB)
func (db *DB) Having(query interface{}, args ...interface{}) (tx *DB)Voici un exemple
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
La méthode Distinct est principalement utilisée pour la déduplication
func (db *DB) Distinct(args ...interface{}) (tx *DB)Voici un exemple
// SELECT DISTINCT `name` FROM `person` WHERE address IN ('cn','us')
db.Where("address IN ?", []string{"cn", "us"}).Distinct("name").Find(&ps)Sous-requête
Une sous-requête est une requête imbriquée, par exemple pour trouver toutes les personnes dont la valeur id est supérieure à la moyenne
// SELECT * FROM `person` WHERE id > (SELECT AVG(id) FROM `person`
db.Where("id > (?)", db.Model(Person{}).Select("AVG(id)")).Find(&ps)Sous-requête 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)Verrou
Gorm utilise la clause clause.Locking pour fournir le support des verrous
// 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)Itération
Via la méthode Rows, vous pouvez obtenir un itérateur
func (db *DB) Rows() (*sql.Rows, error)En parcourant l'itérateur, utilisez la méthode ScanRows pour scanner le résultat de chaque ligne dans une structure.
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
}
}Modification
save
La méthode Save mentionnée lors de la création peut également être utilisée pour mettre à jour des enregistrements, et elle mettra à jour tous les champs, même si certains champs de la structure ont une valeur zéro. Cependant, si la clé primaire ne correspond pas, une opération d'insertion sera effectuée.
var p Person
db.First(&p)
p.Address = "poland"
// UPDATE `person` SET `name`='json',`address`='poland' WHERE `id` = 2
db.Save(&p)On peut voir qu'elle a ajouté tous les champs sauf la clé primaire dans l'instruction SET.
update
Donc dans la plupart des cas, il est recommandé d'utiliser la méthode Update
func (db *DB) Update(column string, value interface{}) (tx *DB)Elle est principalement utilisée pour mettre à jour un seul champ de colonne
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
La méthode Updates est utilisée pour mettre à jour plusieurs colonnes. Elle accepte une structure ou une map comme paramètre. Lorsqu'un champ de structure a une valeur zéro, ce champ sera ignoré, mais pas dans une map.
func (db *DB) Updates(values interface{}) (tx *DB)Voici un exemple
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"})Expression SQL
Parfois, il est nécessaire d'effectuer des opérations d'auto-incrémentation ou de décrémentation sur des champs. Généralement, on interroge d'abord, puis on calcule et met à jour, ou on utilise des expressions SQL.
func Expr(expr string, args ...interface{}) clause.ExprVoici un exemple
// 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")})Suppression
Dans gorm, la méthode Delete est utilisée pour supprimer des enregistrements. Elle peut accepter directement une structure d'entité ou des conditions.
func (db *DB) Delete(value interface{}, conds ...interface{}) (tx *DB)Par exemple, en passant directement une structure
var p Person
db.First(&p)
// // DELETE FROM `person` WHERE `person`.`id` = 2
db.Delete(&p)Ou
var p Person
db.First(&p)
// DELETE FROM `person` WHERE `person`.`id` = 2
db.Model(p).Delete(nil)Ou en spécifiant une condition
// DELETE FROM `person` WHERE id = 2
db.Model(Person{}).Where("id = ?", p.Id).Delete(nil)Peut aussi s'écrire en raccourci
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)Pour une suppression par lot, passez 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})Suppression Douce
Si votre modèle d'entité utilise la suppression douce, lors de la suppression, une opération de mise à jour sera effectuée par défaut. Pour une suppression permanente, utilisez la méthode Unscoped
db.Unscoped().Delete(&Person{}, []uint{1, 2, 3})Définition des Associations
Gorm fournit des capacités d'interaction pour les associations de tables, en définissant les relations entre structures via l'intégration de structures et de champs.
Un-à-Un
La relation un-à-un est la plus simple. Normalement, une personne ne peut avoir qu'une seule mère. Voyons la structure suivante
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 structure Person implémente une référence au type Mom en intégrant la structure Mom, où Person.MomId est le champ de référence et la clé primaire Mom.Id est le champ référencé, complétant ainsi l'association un-à-un. Comment personnaliser les clés étrangères, les références et les contraintes, ainsi que les règles de clé étrangère par défaut, ont déjà été abordés dans Définition de Clé Étrangère, nous n'y reviendrons pas.
TIP
Pour les champs de clé étrangère, il est recommandé d'utiliser les types fournis par le package sql, car les clés étrangères peuvent être NULL par défaut. Lors de la création d'enregistrements avec Create, si vous utilisez des types ordinaires, la valeur zéro 0 sera également créée, ce qui n'est évidemment pas autorisé pour une clé étrangère inexistante.
Un-à-Plusieurs
Ajoutons une structure School. L'école et les étudiants ont une relation un-à-plusieurs : une école a plusieurs étudiants, mais un étudiant ne peut étudier que dans une seule école.
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 est de type []person, indiquant qu'elle peut avoir plusieurs étudiants, tandis que Person doit avoir une clé étrangère contenant la référence à School, c'est-à-dire Person.SchoolId.
Plusieurs-à-Plusieurs
Une personne peut posséder plusieurs maisons, et une maison peut aussi héberger plusieurs personnes, c'est une relation plusieurs-à-plusieurs.
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 et House détiennent mutuellement des types de slice l'un de l'autre, représentant une relation plusieurs-à-plusieurs. Les relations plusieurs-à-plusieurs nécessitent généralement la création d'une table de jointure, spécifiée via many2many. Les clés étrangères de la table de jointure doivent être correctement spécifiées.
Après avoir créé les structures, laissez gorm migrer automatiquement vers la base de données
tables := []any{
School{},
Mom{},
Person{},
House{},
PersonHouse{},
}
for _, table := range tables {
db.Migrator().CreateTable(&table)
}Notez l'ordre de création entre les tables de référence et les tables référencées.
Opérations d'Association
Après avoir créé les trois types de relations d'association ci-dessus, voyons comment utiliser les associations pour les opérations CRUD. Cela utilisera principalement la méthode Association
func (db *DB) Association(column string) *AssociationElle accepte un paramètre d'association, dont la valeur doit être le nom du champ du type référencé intégré dans la structure de référence.
db.Model(&person).Association("Mom").Find(&mom)Par exemple, pour rechercher la mère d'une personne par association, le paramètre de Association est Mom, c'est-à-dire le nom du champ Person.Mom.
Créer une Association
// Définir les données
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)
// Ajouter l'association Person-Mom, association un-à-un
// 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)
// Ajouter l'association School-Person, association un-à-plusieurs
// 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})
// Ajouter l'association Person-Houses, association plusieurs-à-plusieurs
// 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 tous les enregistrements n'existent pas, lors de la création de l'association, gorm créera d'abord les enregistrements puis créera l'association.
Rechercher une Association
Voici comment rechercher des associations.
// Recherche d'association un-à-un
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)
// Recherche d'association un-à-plusieurs
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)
// Recherche d'association plusieurs-à-plusieurs
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 recherche d'association trouve les enregistrements correspondants dans la table de référence en fonction des données existantes. Pour les relations plusieurs-à-plusieurs, gorm effectue automatiquement la jointure de tables.
Mettre à jour une Association
Voici comment mettre à jour des associations
// Mise à jour d'association un-à-un
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)
// Mise à jour d'association un-à-plusieurs
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)
// Mise à jour d'association plusieurs-à-plusieurs
// 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"}})Lors de la mise à jour d'association, si les données référencées et les données de référence n'existent pas, gorm tentera de les créer.
Supprimer une Association
Voici comment supprimer des associations
// Suppression d'association un-à-un
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)
// Suppression d'association un-à-plusieurs
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)
// Suppression d'association plusieurs-à-plusieurs
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)La suppression d'association supprime uniquement la relation de référence entre eux, pas les enregistrements d'entité. Nous pouvons également utiliser la méthode Clear pour vider directement l'association
db.Model(&jack).Association("Houses").Clear()Si vous souhaitez supprimer les enregistrements d'entité correspondants, vous pouvez ajouter l'opération Unscoped après l'opération Association (n'affecte pas many2many)
db.Model(&jack).Association("Houses").Unscoped().Delete(&houses)Pour les relations un-à-plusieurs et plusieurs-à-plusieurs, vous pouvez utiliser l'opération Select pour supprimer des enregistrements
var (
mit School
)
db.Where("name = ?", "mit").First(&mit)
db.Select("Persons").Delete(&mit)Préchargement
Le préchargement est utilisé pour interroger les données d'association. Pour les entités ayant des relations d'association, il précharge d'abord les entités référencées. La requête d'association mentionnée précédemment interroge la relation d'association, tandis que le préchargement interroge directement les enregistrements d'entité, y compris toutes les relations d'association. D'un point de vue syntaxique, la requête d'association doit d'abord interroger le []Person spécifié, puis interroger le []Mom associé basé sur le []Person. Le préchargement interroge syntaxiquement directement le []Person et charge également toutes les relations d'association. Cependant, en réalité, le SQL exécuté est à peu près le même. Voici un exemple
var users []Person
// SELECT * FROM `moms` WHERE `moms`.`id` = 1
// SELECT * FROM `people`
db.Preload("Mom").Find(&users)C'est un exemple de requête d'association un-à-un, sa sortie
[{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:[]}]On peut voir que le Mom associé a été interrogé également, mais sans préchargement de la relation école, toutes les structures School ont des valeurs zéro. Vous pouvez également utiliser clause.Associations pour indiquer le préchargement de toutes les relations, à l'exception des relations imbriquées.
db.Preload(clause.Associations).Find(&users)Voici un exemple de préchargement imbriqué. Son rôle est d'interroger tous les étudiants associés à chaque école, la mère associée à chaque étudiant, les maisons possédées par chaque étudiant, et également l'ensemble des propriétaires de chaque maison. École->Étudiant->Maison->Étudiant.
var schools []School
db.Preload("Persons").
Preload("Persons.Mom").
Preload("Persons.Houses").
Preload("Persons.Houses.Persons").Find(&schools)
// Code de sortie, la logique peut être ignorée
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 sortie est
school MIT
person jack
mom jenny
house h1 owner [jack]
house h2 owner [jack]
person mike
momOn peut voir que la sortie inclut la mère de chaque étudiant de chaque école, leurs maisons, et tous les propriétaires des maisons.
Transactions
Gorm active les transactions par défaut. Toute opération d'insertion ou de mise à jour qui échoue sera annulée (rollback). Cela peut être désactivé dans la Configuration de Connexion, ce qui améliore les performances d'environ 30%. Il existe plusieurs méthodes pour utiliser les transactions dans gorm, présentées ci-dessous.
Automatique
Transaction par fermeture, via la méthode Transaction, en passant une fonction de fermeture. Si la fonction retourne une valeur non nil, un rollback automatique sera effectué.
func (db *DB) Transaction(fc func(tx *DB) error, opts ...*sql.TxOptions) (err error)Voici un exemple. Les opérations dans la fermeture doivent utiliser le paramètre tx, et non le db externe.
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
})Manuelle
L'utilisation de transactions manuelles est recommandée, nous permettant de contrôler quand effectuer un rollback et quand valider (commit). Les transactions manuelles utilisent les trois méthodes suivantes
// La méthode Begin démarre une transaction
func (db *DB) Begin(opts ...*sql.TxOptions) *DB
// La méthode Rollback annule la transaction
func (db *DB) Rollback() *DB
// La méthode Commit valide la transaction
func (db *DB) Commit() *DBVoici un exemple. Après avoir démarré une transaction, vous devez utiliser tx pour opérer l'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()Vous pouvez spécifier un point de rollback
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()Conclusion
Si vous avez lu tout le contenu ci-dessus et avez pratiqué le code, vous pouvez désormais utiliser gorm pour effectuer des opérations CRUD sur la base de données. En plus de ces opérations, gorm offre de nombreuses autres fonctionnalités. Pour plus de détails, vous pouvez consulter la documentation officielle.
