Skip to content

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

sh
$ go get -u gorm.io/gorm

Connexion

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.

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

Ensuite, 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.

go
package main

import (
  "gorm.io/driver/mysql"
  "gorm.io/gorm"
  "log/slog"
)

func main() {
  dsn := "root:123456@tcp(192.168.48.138:3306)/hello?charset=utf8mb4&parseTime=True&loc=Local"
  db, err := gorm.Open(mysql.Open(dsn))
  if err != nil {
    slog.Error("db connect error", err)
  }
  slog.Info("db connect success")
}

Ou passer la configuration manuellement

go
package main

import (
  "gorm.io/driver/mysql"
  "gorm.io/gorm"
  "log/slog"
)

func main() {
  db, err := gorm.Open(mysql.New(mysql.Config{}))
  if err != nil {
    slog.Error("db connect error", err)
  }
  slog.Info("db connect success")
}

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

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

Voici quelques explications simples, vous pouvez configurer selon vos besoins.

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

go
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é.

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

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

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

func (p Person) TableName() string {
  return "person"
}

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

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

  CreatedAt sql.NullTime
  UpdatedAt sql.NullTime
}

func (p Person) TableName() string {
  return "person"
}

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.

go
db.Create(&Person{
    Name:    "jack",
    Address: "usa",
    Mom:     "lili",
    Dad:     "tom",
  })

// INSERT INTO `person` (`name`,`address`,`mom`,`dad`,`created_at`,`updated_at`) VALUES ('jack','usa','lili','tom','2023-10-25 14:43:57.16','2023-10-25 14:43:57.16')

Gorm supporte également le suivi par timestamp

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

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

Ainsi, lors de l'exécution de Create, cela équivaut au SQL suivant

sql
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.

go
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é.

go
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

go
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

go
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

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

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

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

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

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

Clé Étrangère

La définition de relations de clé étrangère dans une structure se fait en intégrant des structures, par exemple

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

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

  DadId uint
  Dad   Dad `gorm:"foreignKey:DadId;"`
}

type Mom struct {
  Id   uint
  Name string

  Persons []Person `gorm:"foreignKey:MomId;"`
}

type Dad struct {
  Id   uint
  Name string

  Persons []Person `gorm:"foreignKey:DadId;"`
}

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.

go
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

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

  MomId uint
  Mom   Mom `gorm:"foreignKey:MomId;references:Sid;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`

  DadId uint
  Dad   Dad `gorm:"foreignKey:DadId;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}

type Mom struct {
  Id   uint
  Sid  uint `gorm:"uniqueIndex;"`
  Name string

  Persons []Person `gorm:"foreignKey:MomId;"`
}

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

go
// 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 tagDescription
columnSpécifie le nom de colonne db
typeType 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
serializerSpé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
sizeDéfinit la taille ou la longueur du type de données de colonne, par exemple size: 256
primaryKeyDéfinit la colonne comme clé primaire
uniqueDéfinit la colonne comme clé unique
defaultDéfinit la valeur par défaut de la colonne
precisionSpécifie la précision de la colonne
scaleSpécifie l'échelle de la colonne
not nullSpécifie la colonne comme NOT NULL
autoIncrementSpécifie la colonne comme auto-incrémentée
autoIncrementIncrementPas d'auto-incrémentation, contrôle l'intervalle entre les enregistrements consécutifs
embeddedChamp imbriqué
embeddedPrefixPréfixe du nom de colonne pour les champs imbriqués
autoCreateTimeSuit 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
autoUpdateTimeSuit 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
indexCré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
uniqueIndexIdentique à index, mais crée un index unique
checkCré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
commentAjoute un commentaire au champ lors de la migration
foreignKeySpécifie la colonne du modèle actuel comme clé étrangère de la table de jointure
referencesSpécifie le nom de colonne de la table référencée, qui sera mappé comme clé étrangère de la table de jointure
polymorphicSpécifie le type polymorphique, comme le nom du modèle
polymorphicValueSpécifie la valeur polymorphique, nom de table par défaut
many2manySpécifie le nom de la table de jointure
joinForeignKeySpécifie le nom de colonne de clé étrangère dans la table de jointure, qui sera mappé à la table actuelle
joinReferencesSpécifie le nom de colonne de clé étrangère dans la table de jointure, qui sera mappé à la table référencée
constraintContrainte 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.

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

Par exemple

go
type Person struct {
  Id      uint   `gorm:"primaryKey;"`
  Name    string `gorm:"type:varchar(100);uniqueIndex;"`
  Address string
}

type Order struct {
  Id   uint
  Name string
}

db.AutoMigrate(Person{}, Order{})
// CREATE TABLE `person` (`id` bigint unsigned AUTO_INCREMENT,`name` varchar(100),`address` longtext,PRIMARY KEY (`id`),UNIQUE INDEX `idx_person_name` (`name`))
// CREATE TABLE `orders` (`id` bigint unsigned AUTO_INCREMENT,`name` longtext,PRIMARY KEY (`id`))

Ou vous pouvez également effectuer des opérations manuelles via la méthode Migrator pour accéder à l'interface Migrator

go
func (db *DB) Migrator() Migrator

Elle supporte les méthodes d'interface suivantes

go
type Migrator interface {
  // AutoMigrate
  AutoMigrate(dst ...interface{}) error

  // Database
  CurrentDatabase() string
  FullDataTypeOf(*schema.Field) clause.Expr
  GetTypeAliases(databaseTypeName string) []string

  // Tables
  CreateTable(dst ...interface{}) error
  DropTable(dst ...interface{}) error
  HasTable(dst interface{}) bool
  RenameTable(oldName, newName interface{}) error
  GetTables() (tableList []string, err error)
  TableType(dst interface{}) (TableType, error)

  // Columns
  AddColumn(dst interface{}, field string) error
  DropColumn(dst interface{}, field string) error
  AlterColumn(dst interface{}, field string) error
  MigrateColumn(dst interface{}, field *schema.Field, columnType ColumnType) error
  HasColumn(dst interface{}, field string) bool
  RenameColumn(dst interface{}, oldName, field string) error
  ColumnTypes(dst interface{}) ([]ColumnType, error)

  // Views
  CreateView(name string, option ViewOption) error
  DropView(name string) error

  // Constraints
  CreateConstraint(dst interface{}, name string) error
  DropConstraint(dst interface{}, name string) error
  HasConstraint(dst interface{}, name string) bool

  // Indexes
  CreateIndex(dst interface{}, name string) error
  DropIndex(dst interface{}, name string) error
  HasIndex(dst interface{}, name string) bool
  RenameIndex(dst interface{}, oldName, newName string) error
  GetIndexes(dst interface{}) ([]Index, error)
}

La 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

go
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

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

Soit la structure suivante

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

Créer un enregistrement

go
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.RowsAffected

Aprè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

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

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

go
func (db *DB) Save(value interface{}) (tx *DB)
go
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

go
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

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

Ou mettre à jour tous les champs directement

go
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

go
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

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

Passer 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.

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

Pluck

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é.

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

Par exemple, collecter toutes les adresses des personnes dans un slice de chaînes

go
var adds []string

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

C'est en fait équivalent à

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

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

Voici un exemple d'utilisation

go
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

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

Elle trouve tous les enregistrements correspondant aux conditions données

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

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

Par exemple

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

Équivalent à

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

De plus, vous pouvez utiliser la méthode Omit pour ignorer des champs

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

Par exemple

go
// SELECT `person`.`id`,`person`.`name` FROM `person` WHERE id IN (1,2,3,4)
db.Omit("address").Where("id IN ?", []int{1, 2, 3, 4}).Find(&ps)``

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

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

Voici un exemple simple

go
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

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

Ou utiliser la méthode Or pour construire des instructions OR

go
func (db *DB) Or(query interface{}, args ...interface{}) (tx *DB)
go
// SELECT * FROM `person` WHERE id = 1 OR name = 'jack' AND address = 'usa' ORDER BY `person`.`id` LIMIT 1
db.Where("id = ?", 1).
    Or("name = ?", "jack").
    Where("address = ?", "usa").
    First(&p)

Il y a aussi la méthode Not, qui est similaire

go
func (db *DB) Not(query interface{}, args ...interface{}) (tx *DB)
go
// SELECT * FROM `person` WHERE id = 1 OR name = 'jack' AND NOT name = 'mike' AND address = 'usa' ORDER BY `person`.`id` LIMIT 1
db.Where("id = ?", 1).
    Or("name = ?", "jack").
    Not("name = ?", "mike").
    Where("address = ?", "usa").
    First(&p)

Pour les conditions IN, vous pouvez directement passer un slice dans la méthode Where.

go
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

go
// SELECT * FROM `person` WHERE (id, name, address) IN ((1,'jack','uk'),(2,'mike','usa'))
db.Where("(id, name, address) IN ?", [][]any{{1, "jack", "uk"}, {2, "mike", "usa"}}).Find(&ps)

Gorm supporte le groupement de where, c'est-à-dire combiner les instructions ci-dessus

go
db.Where(
    db.Where("name IN ?", []string{"cn", "uk"}).Where("id IN ?", []uint{1, 2}),
  ).Or(
    db.Where("name IN ?", []string{"usa", "jp"}).Where("id IN ?", []uint{3, 4}),
  ).Find(&ps)
// SELECT * FROM `person` WHERE (name IN ('cn','uk') AND id IN (1,2)) OR (name IN ('usa','jp') AND id IN (3,4))

Order

Le tri utilise la méthode Order

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

Voici un exemple d'utilisation

go
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

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

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

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

Voici un exemple simple de pagination

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

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

Group

Les méthodes Group et Having sont principalement utilisées pour les opérations de groupement

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

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

Voici un exemple

go
var (
    ps []Person
)

// SELECT `address` FROM `person` GROUP BY `address` HAVING address IN ('cn','us')
db.Select("address").Group("address").Having("address IN ?", []string{"cn", "us"}).Find(&ps)

Distinct

La méthode Distinct est principalement utilisée pour la déduplication

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

Voici un exemple

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

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

go
// SELECT * FROM (SELECT * FROM `person` WHERE address IN ('cn','uk')) as p
db.Table("(?) as p", db.Model(Person{}).Where("address IN ?", []string{"cn", "uk"})).Find(&ps)

Verrou

Gorm utilise la clause clause.Locking pour fournir le support des verrous

go
// SELECT * FROM `person` FOR UPDATE
db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&ps)

// SELECT * FROM `person` FOR SHARE NOWAIT
db.Clauses(clause.Locking{Strength: "SHARE", Options: "NOWAIT"}).Find(&ps)

Itération

Via la méthode Rows, vous pouvez obtenir un itérateur

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

go
rows, err := db.Model(Person{}).Rows()
if err != nil {
    return
}
defer rows.Close()

for rows.Next() {
    var p Person
    err := db.ScanRows(rows, &p)
    if err != nil {
        return
    }
}

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.

go
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

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

Elle est principalement utilisée pour mettre à jour un seul champ de colonne

go
var p Person

db.First(&p)

// UPDATE `person` SET `address`='poland' WHERE id = 2
db.Model(Person{}).Where("id = ?", p.Id).Update("address", "poland")

updates

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.

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

Voici un exemple

go
var p Person

db.First(&p)

// UPDATE `person` SET `name`='jojo',`address`='poland' WHERE `id` = 2
db.Model(p).Updates(Person{Name: "jojo", Address: "poland"})

// UPDATE `person` SET `address`='poland',`name`='jojo' WHERE `id` = 2
db.Model(p).Updates(map[string]any{"name": "jojo", "address": "poland"})

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.

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

Voici un exemple

go
// UPDATE `person` SET `age`=age + age,`name`='jojo' WHERE `id` = 2
db.Model(p).Updates(map[string]any{"name": "jojo", "age": gorm.Expr("age + age")})

// UPDATE `person` SET `age`=age * 2 + age,`name`='jojo' WHERE `id` = 2
db.Model(p).Updates(map[string]any{"name": "jojo", "age": gorm.Expr("age * 2 + age")})

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.

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

Par exemple, en passant directement une structure

go
var p Person

db.First(&p)

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

Ou

go
var p Person

db.First(&p)

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

Ou en spécifiant une condition

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

Peut aussi s'écrire en raccourci

go
var p Person

db.First(&p)

// DELETE FROM `person` WHERE id = 2
db.Delete(&Person{}, "id = ?", 2)

// DELETE FROM `person` WHERE `person`.`id` = 2
db.Delete(&Person{}, 2)

Pour une suppression par lot, passez un slice

go
// DELETE FROM `person` WHERE id IN (1,2,3)
db.Delete(&Person{}, "id IN ?", []uint{1, 2, 3})
// DELETE FROM `person` WHERE `person`.`id` IN (1,2,3)
db.Delete(&Person{}, []uint{1, 2, 3})

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

go
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

go
type Person struct {
  Id      uint
  Name    string
  Address string
  Age     uint

  MomId sql.NullInt64
  Mom   Mom `gorm:"foreignKey:MomId;"`
}

type Mom struct {
  Id   uint
  Name string
}

La 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.

go
type Person struct {
    Id      uint
    Name    string
    Address string
    Age     uint

    MomId sql.NullInt64
    Mom   Mom `gorm:"foreignKey:MomId;"`

    SchoolId sql.NullInt64
    School   School `gorm:"foreignKey:SchoolId;"`
}

type Mom struct {
    Id   uint
    Name string
}


type School struct {
    Id   uint
    Name string

    Persons []Person `gorm:"foreignKey:SchoolId;"`
}

school.Persons 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.

go
type Person struct {
  Id      uint
  Name    string
  Address string
  Age     uint

  MomId sql.NullInt64
  Mom   Mom `gorm:"foreignKey:MomId;"`

  SchoolId sql.NullInt64
  School   School `gorm:"foreignKey:SchoolId;"`

  Houses []House `gorm:"many2many:person_house;"`
}

type Mom struct {
  Id   uint
  Name string
}

type School struct {
  Id   uint
  Name string

  Persons []Person
}

type House struct {
  Id   uint
  Name string

  Persons []Person `gorm:"many2many:person_house;"`
}

type PersonHouse struct {
  PersonId sql.NullInt64
  Person   Person `gorm:"foreignKey:PersonId;"`
  HouseId  sql.NullInt64
  House    House `gorm:"foreignKey:HouseId;"`
}

Person 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

go
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

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

Elle 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.

go
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

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

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

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

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

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

go
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

go
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

go
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

go
[{Id:1 Name:jack Address:usa Age:18 MomId:{Int64:1 Valid:true} Mom:{Id:1 Name:jenny} SchoolId:{Int64:1 Valid:true} School:{Id:0 Name: Persons:[]} Houses:[]} {Id:2 Name:mike Address:uk Age:20 MomId:{Int64:0 Valid:false} Mom:{Id:0 Name:} SchoolId:{Int64:1 Valid:true} School:{Id:0 Name: Persons:[]} Houses:[]}]

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.

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

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

On 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é.

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

go
var ps []Person

db.Transaction(func(tx *gorm.DB) error {
    err := tx.Create(&ps).Error
    if err != nil {
        return err
    }

    err = tx.Create(&ps).Error
    if err != nil {
        return err
    }

    err = tx.Model(Person{}).Where("id = ?", 1).Update("name", "jack").Error
    if err != nil {
        return err
    }

    return nil
})

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

go
// 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() *DB

Voici un exemple. Après avoir démarré une transaction, vous devez utiliser tx pour opérer l'ORM.

go
var ps []Person

tx := db.Begin()

err := tx.Create(&ps).Error
if err != nil {
    tx.Rollback()
    return
}

err = tx.Create(&ps).Error
if err != nil {
    tx.Rollback()
    return
}

err = tx.Model(Person{}).Where("id = ?", 1).Update("name", "jack").Error
if err != nil {
    tx.Rollback()
    return
}

tx.Commit()

Vous pouvez spécifier un point de rollback

go
var ps []Person

tx := db.Begin()

err := tx.Create(&ps).Error
if err != nil {
    tx.Rollback()
    return
}

tx.SavePoint("createBatch")

err = tx.Create(&ps).Error
if err != nil {
    tx.Rollback()
    return
}

err = tx.Model(Person{}).Where("id = ?", 1).Update("name", "jack").Error
if err != nil {
    tx.RollbackTo("createBatch")
    return
}

tx.Commit()

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.

Golang by www.golangdev.cn edit