Skip to content

Gorm Datenbank ORM Bibliothek

Offizielle Dokumentation: GORM - The fantastic ORM library for Golang, aims to be developer friendly.

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

In der Go-Community gibt es bei der Datenbankinteraktion zwei Lager: Das eine bevorzugt einfache Bibliotheken wie sqlx, die nicht so mächtig sind, aber dem Entwickler volle Kontrolle über SQL geben und die Performance auf das Maximum optimieren. Das andere Lager bevorzugt ORM, das für Entwicklungseffizienz entwickelt wurde und viele unnötige Probleme während der Entwicklung erspart. Wenn es um ORM geht, kommt man in der Go-Community an gorm nicht vorbei. Es ist ein sehr etabliertes ORM, ähnlich wie das relativ jüngere xorm, ent usw. Dieser Artikel behandelt die Inhalte von gorm. Hier werden nur die Grundlagen erklärt, als Einstiegshilfe. Für tiefere Details können Sie die offizielle Dokumentation lesen, die bereits sehr vollständig auf Deutsch verfügbar ist.

Eigenschaften

  • Vollständiges ORM
  • Assoziationen (Has One, Has Many, Belongs To, Many To Many, Polymorph, Single Table Inheritance)
  • Hook-Methoden in Create, Save, Update, Delete, Find
  • Unterstützung für Preloading mit Preload, Joins
  • Transaktionen, verschachtelte Transaktionen, Save Point, Rollback To Saved Point
  • Context, Prepared Statement Modus, DryRun Modus
  • Batch Insert, FindInBatches, Find/Create with Map, CRUD mit SQL-Ausdrücken, Context Valuer
  • SQL Builder, Upsert, Locking, Optimizer/Index/Comment Hint, benannte Parameter, Unterabfragen
  • Zusammengesetzte Primärschlüssel, Indizes, Constraints
  • Auto Migration
  • Benutzerdefinierter Logger
  • Flexible erweiterbare Plugin API: Database Resolver (mehrere Datenbanken, Read-Write-Splitting), Prometheus...
  • Jedes Feature wurde ausführlich getestet
  • Entwicklerfreundlich

Gorm hat natürlich auch einige Nachteile. Zum Beispiel sind fast alle Methodenparameter vom Typ leerer Interface, ohne Dokumentation weiß man oft gar nicht, welche Parameter man übergeben soll. Manchmal kann man Strukturen übergeben, manchmal Strings, manchmal Maps, manchmal Slices - die Semantik ist ziemlich vage, und in vielen Fällen muss man SQL trotzdem selbst schreiben.

Als Alternativen gibt es zwei ORMs, die Sie ausprobieren können: Das erste ist aorm, erst kürzlich open-source gestellt. Es erfordert nicht mehr das manuelle Schreiben von Tabellenfeldnamen, in den meisten Fällen ist es eine verkettete Operation, basierend auf Reflexion implementiert. Da die Anzahl der Stars noch gering ist, kann man noch abwarten. Das zweite ist ent, ein von facebook open-source gestelltes ORM. Es unterstützt ebenfalls verkettete Operationen und in den meisten Fällen muss man SQL nicht selbst schreiben. Das Design basiert auf Graphen (aus der Datenstruktur), die Implementierung basiert auf Codegenerierung statt Reflexion (was ich bevorzuge), aber die Dokumentation ist komplett auf Englisch, was eine gewisse Einstiegshürde darstellt.

Installation

Installation der gorm Bibliothek

sh
$ go get -u gorm.io/gorm

Verbindung

Gorm unterstützt derzeit folgende Datenbanken

  • 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 ist kompatibel mit dem MySQL-Protokoll
  • ClickHouse: "gorm.io/driver/clickhouse"

Darüber hinaus gibt es einige andere Datenbanktreiber, die von Drittanbietern bereitgestellt werden, wie z.B. der Oracle-Treiber CengSin/oracle. In diesem Artikel wird MySQL für die Demonstration verwendet. Welche Datenbank Sie verwenden, müssen Sie den entsprechenden Treiber installieren. Hier installieren wir den MySQL-gorm-Treiber.

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

Verwenden Sie dann DSN (Data Source Name), um sich mit der Datenbank zu verbinden. Die Treiberbibliothek analysiert den DSN automatisch in die entsprechende Konfiguration.

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

Oder manuelle Konfigurationsübergabe

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

Beide Methoden sind gleichwertig, je nach persönlicher Vorliebe.

Verbindungskonfiguration

Durch Übergabe der gorm.Config Konfigurationsstruktur können wir das Verhalten von gorm steuern.

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

Im Folgenden einige einfache Erklärungen, die Sie je nach Bedarf konfigurieren können.

go
type Config struct {
  // Deaktiviert Standard-Transaktion, gorm startet bei einzelnen Create- und Update-Operationen Transaktionen für Datenkonsistenz
  SkipDefaultTransaction bool
  // Benutzerdefinierte Namensstrategie
  NamingStrategy schema.Namer
  // Speichert vollständige Assoziationen
  FullSaveAssociations bool
  // Benutzerdefinierter Logger
  Logger logger.Interface
  // Benutzerdefinierte NowFunc zum Befüllen von CreatedAt und UpdatedAt Feldern
  NowFunc func() time.Time
  // Nur SQL generieren, nicht ausführen
  DryRun bool
  // Verwende Prepared Statements
  PrepareStmt bool
  // Nach Verbindungsaufbau die Datenbank pingen
  DisableAutomaticPing bool
  // Bei Migration Fremdschlüssel ignorieren
  DisableForeignKeyConstraintWhenMigrating bool
  // Bei Migration Assoziationsreferenzen ignorieren
  IgnoreRelationshipsWhenMigrating bool
  // Verschachtelte Transaktionen deaktivieren
  DisableNestedTransaction bool
  // Globale Updates erlauben, also Update ohne WHERE
  AllowGlobalUpdate bool
  // Alle Tabellenfelder abfragen
  QueryFields bool
  // Batch-Create Größe
  CreateBatchSize int
  // Fehlerkonvertierung aktivieren
  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
}

Modell

In gorm entspricht ein Modell einer Datenbanktabelle, normalerweise dargestellt durch eine Struktur, wie im folgenden Beispiel.

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

Das Innere der Struktur kann aus Basisdatentypen und Typen bestehen, die die Interfaces sql.Scanner und sql.Valuer implementieren. Standardmäßig heißt die Tabelle, der die Person-Struktur zugeordnet ist, persons, im Snake-Case-Plural-Stil, mit Unterstrichen getrennt. Auch die Spaltennamen sind im Snake-Case-Stil, z.B. entspricht Id dem Spaltennamen id. Gorm bietet auch Möglichkeiten zur Konfiguration.

Spaltennamen angeben

Durch Struktur-Tags können wir Spaltennamen für Strukturfelder angeben, sodass gorm bei der Entity-Mapping die angegebenen Spaltennamen verwendet.

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

Tabellennamen angeben

Durch Implementierung des Table-Interfaces kann der Tabellenname angegeben werden. Es hat nur eine Methode, die den Tabellennamen zurückgibt.

go
type Tabler interface {
  TableName() string
}

In der implementierten Methode wird der String person zurückgegeben. Bei der Datenbankmigration erstellt gorm eine Tabelle namens 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"
}

Für die Namensstrategie können Sie auch bei der Verbindungserstellung Ihre eigene Strategie-Implementierung übergeben, um benutzerdefinierte Effekte zu erzielen.

Zeit-Tracking

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

Wenn die Felder CreatedAt oder UpdatedAt enthalten sind und bei der Erstellung oder Aktualisierung von Datensätzen den Nullwert haben, setzt gorm automatisch die Zeit mit time.Now().

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 unterstützt auch Timestamp-Tracking:

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

Bei Ausführung von Create ist dies äquivalent zu folgendem SQL:

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

In der Praxis empfehle ich, bei Zeit-Tracking-Anforderungen Timestamps im Backend zu speichern, da die Handhabung bei Zeitzonenübergängen einfacher ist.

Model

Gorm bietet eine voreingestellte Model-Struktur, die einen ID-Primärschlüssel, zwei Zeit-Tracking-Felder und ein Feld für Soft-Delete enthält.

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

Bei der Verwendung betten Sie diese einfach in Ihr Entity-Modell ein.

go
type Order struct {
  gorm.Model
  Name string
}

So erhält es automatisch alle Eigenschaften von gorm.Model.

Primärschlüssel

Standardmäßig ist das Feld namens Id der Primärschlüssel. Mit Struktur-Tags können Primärschlüsselfelder angegeben werden:

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

  CreatedAt sql.NullTime
  UpdatedAt sql.NullTime
}

Mehrere Felder bilden einen zusammengesetzten Primärschlüssel:

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

Durch das index Struktur-Tag können Spaltenindizes angegeben werden:

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

In der obigen Struktur wurde für das Feld Address ein eindeutiger Index erstellt. Zwei Felder mit demselben Indexnamen erstellen einen zusammengesetzten Index:

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

Fremdschlüssel

In der Struktur werden Fremdschlüsselbeziehungen durch Einbettung von Strukturen definiert, zum Beispiel:

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

Im Beispiel hat die Person-Struktur zwei Fremdschlüssel, die auf die Primärschlüssel der Dad- und Mom-Strukturen verweisen. Standardmäßig wird auf den Primärschlüssel verwiesen. Person hat eine 1:1-Beziehung zu Dad und Mom - eine Person kann nur einen Vater und eine Mutter haben. Dad und Mom haben eine 1:n-Beziehung zu Person, da Vater und Mutter mehrere Kinder haben können.

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

Die eingebettete Struktur dient dazu, Fremdschlüssel und Referenzen einfach anzugeben. Standardmäßig lautet das Fremdschlüsselfeld <ReferenzierterTypName>Id, z.B. MomId. Standardmäßig wird auf den Primärschlüssel verwiesen. Mit Struktur-Tags kann auf ein bestimmtes Feld verwiesen werden:

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

Dabei definiert constraint:OnUpdate:CASCADE,OnDelete:SET NULL; die Fremdschlüssel-Constraints.

Hooks

Ein Entity-Modell kann benutzerdefinierte Hooks definieren für:

  • Create
  • Update
  • Delete
  • Query

Die entsprechenden Interfaces sind:

go
// Wird vor dem Erstellen ausgelöst
type BeforeCreateInterface interface {
    BeforeCreate(*gorm.DB) error
}

// Wird nach dem Erstellen ausgelöst
type AfterCreateInterface interface {
    AfterCreate(*gorm.DB) error
}

// Wird vor dem Aktualisieren ausgelöst
type BeforeUpdateInterface interface {
    BeforeUpdate(*gorm.DB) error
}

// Wird nach dem Aktualisieren ausgelöst
type AfterUpdateInterface interface {
    AfterUpdate(*gorm.DB) error
}

// Wird vor dem Speichern ausgelöst
type BeforeSaveInterface interface {
    BeforeSave(*gorm.DB) error
}

// Wird nach dem Speichern ausgelöst
type AfterSaveInterface interface {
    AfterSave(*gorm.DB) error
}

// Wird vor dem Löschen ausgelöst
type BeforeDeleteInterface interface {
    BeforeDelete(*gorm.DB) error
}

// Wird nach dem Löschen ausgelöst
type AfterDeleteInterface interface {
    AfterDelete(*gorm.DB) error
}

// Wird nach der Abfrage ausgelöst
type AfterFindInterface interface {
    AfterFind(*gorm.DB) error
}

Durch Implementierung dieser Interfaces können Strukturen benutzerdefiniertes Verhalten definieren.

Tags

Im Folgenden sind einige von gorm unterstützte Tags aufgeführt:

Tag-NameBeschreibung
columnGibt den DB-Spaltennamen an
typeSpaltendatentyp, empfohlen werden kompatible generische Typen verwendet, z.B.: alle Datenbanken unterstützen bool, int, uint, float, string, time, bytes und können mit anderen Tags verwendet werden, z.B.: not null, size, autoIncrement… Auch datenbankspezifische Typen wie varbinary(8) werden unterstützt. Bei Verwendung datenbankspezifischer Typen muss der vollständige Typ angegeben werden, z.B.: MEDIUMINT UNSIGNED not NULL AUTO_INCREMENT
serializerGibt den Serialisierer an, um Daten zu serialisieren/deserialisieren, z.B.: serializer:json/gob/unixtime
sizeDefiniert die Größe oder Länge des Spaltendatentyps, z.B. size: 256
primaryKeyDefiniert die Spalte als Primärschlüssel
uniqueDefiniert die Spalte als eindeutigen Schlüssel
defaultDefiniert den Standardwert der Spalte
precisionGibt die Genauigkeit der Spalte an
scaleGibt die Spaltengröße an
not nullGibt an, dass die Spalte NOT NULL ist
autoIncrementGibt an, dass die Spalte automatisch inkrementiert wird
autoIncrementIncrementAutomatische Schrittweite, steuert den Abstand zwischen aufeinanderfolgenden Datensätzen
embeddedEingebettetes Feld
embeddedPrefixPräfix für Spaltennamen eingebetteter Felder
autoCreateTimeVerfolgt die aktuelle Zeit beim Erstellen, für int-Felder werden Timestamp-Sekunden verfolgt, Sie können nano/milli verwenden, um Nanosekunden/Millisekunden zu verfolgen, z.B.: autoCreateTime:nano
autoUpdateTimeVerfolgt die aktuelle Zeit beim Erstellen/Aktualisieren, für int-Felder werden Timestamp-Sekunden verfolgt, Sie können nano/milli verwenden, um Nanosekunden/Millisekunden zu verfolgen, z.B.: autoUpdateTime:milli
indexErstellt einen Index basierend auf Parametern, mehrere Felder mit demselben Namen erstellen einen zusammengesetzten Index, siehe Index für Details
uniqueIndexWie index, erstellt aber einen eindeutigen Index
checkErstellt eine Check-Constraint, z.B. check:age > 13, siehe Constraints für Details
<-Setzt die Schreibberechtigung für Felder, <-:create nur Erstellen, <-:update nur Aktualisieren, <-:false keine Schreibberechtigung, <- Erstellen und Aktualisieren
->Setzt die Leseberechtigung für Felder, ->:false keine Leseberechtigung
-Ignoriert das Feld, - bedeutet kein Lesen/Schreiben, -:migration bedeutet keine Migrationsberechtigung, -:all bedeutet keine Lese-/Schreib-/Migrationsberechtigung
commentFügt bei Migration einen Kommentar für das Feld hinzu
foreignKeyGibt die Spalte des aktuellen Modells als Fremdschlüssel der Verbindungstabelle an
referencesGibt den Spaltennamen der Referenztabelle an, der auf den Fremdschlüssel der Verbindungstabelle abgebildet wird
polymorphicGibt den polymorphen Typ an, z.B. den Modellnamen
polymorphicValueGibt den polymorphen Wert an, Standard Tabellenname
many2manyGibt den Namen der Verbindungstabelle an
joinForeignKeyGibt den Fremdschlüsselspaltennamen der Verbindungstabelle an, der auf die aktuelle Tabelle abgebildet wird
joinReferencesGibt den Fremdschlüsselspaltennamen der Verbindungstabelle an, der auf die Referenztabelle abgebildet wird
constraintBeziehungs-Constraint, z.B.: OnUpdate, OnDelete

Migration

Die AutoMigrate-Methode hilft bei der automatischen Migration. Sie erstellt Tabellen, Constraints, Indizes, Fremdschlüssel usw.

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

Beispiel:

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

Oder Sie können manuell über die Migrator-Methode auf das Migrator-Interface zugreifen:

go
func (db *DB) Migrator() Migrator

Es unterstützt folgende Interface-Methoden:

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

Die Methodenliste deckt mehrere Dimensionen ab: Datenbank, Tabellen, Spalten, Views, Indizes, Constraints, was Benutzern, die Anpassungen benötigen, feinere Kontrolle ermöglicht.

Tabellenkommentar angeben

Bei der Migration können Sie Tabellenkommentare wie folgt hinzufügen:

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

Beachten Sie, dass bei Verwendung der AutoMigrate()-Methode mit referenzierenden Strukturen gorm rekursiv zuerst die referenzierten Tabellen erstellt, was dazu führt, dass die Kommentare der referenzierten und der referenzierenden Tabellen dupliziert werden. Daher wird empfohlen, die CreateTable-Methode zu verwenden.

TIP

Beim Erstellen von Tabellen mit CreateTable müssen Sie sicherstellen, dass referenzierte Tabellen vor referenzierenden Tabellen erstellt werden, sonst tritt ein Fehler auf. Die AutoMigrate-Methode erfordert dies nicht, da sie rekursiv entlang der Referenzbeziehungen erstellt.

Create

Create

Beim Erstellen neuer Datensätze wird in den meisten Fällen die Create-Methode verwendet:

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

Gegeben sei folgende Struktur:

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

Erstellen eines Datensatzes:

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

// Muss als Referenz übergeben werden
db = db.Create(&user)

// Fehler während der Ausführung
err = db.Error
// Anzahl der erstellten Datensätze
affected := db.RowsAffected

Nach der Erstellung schreibt gorm den Primärschlüssel in die User-Struktur, weshalb ein Zeiger übergeben werden muss. Wenn ein Slice übergeben wird, erfolgt ein Batch-Create:

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

db = db.Create(&user)

Ebenso schreibt gorm die Primärschlüssel in das Slice. Bei großen Datenmengen kann auch die CreateInBatches-Methode verwendet werden, um stapelweise zu erstellen, da die generierte SQL-Anweisung INSERT INTO table VALUES (),() sehr lang werden kann und jede Datenbank SQL-Längenbeschränkungen hat.

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

Darüber hinaus kann auch die Save-Methode Datensätze erstellen. Ihre Funktion ist: Bei übereinstimmendem Primärschlüssel wird aktualisiert, sonst eingefügt.

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

db = db.Save(&user)

Upsert

Die Save-Methode kann nur auf Primärschlüssel prüfen. Durch Erstellen von Clause können Sie benutzerdefinierte Upserts erreichen. Zum Beispiel:

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

Dies bewirkt: Wenn das Feld name einen Konflikt verursacht, wird der Wert des Feldes address aktualisiert. Ohne Konflikt wird ein neuer Datensatz erstellt. Sie können bei Konflikten auch nichts tun:

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

Oder alle Felder aktualisieren:

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

Vor der Verwendung von Upsert sollten Sie dem Konfliktfeld einen Index hinzufügen.

Query

First

Für Abfragen bietet gorm viele Methoden. Die erste ist First:

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

Sie findet den ersten Datensatz nach Primärschlüssel aufsteigend, zum Beispiel:

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

Der dest-Zeiger ermöglicht es gorm, die abgefragten Daten auf die Struktur abzubilden.

Oder verwenden Sie die Methoden Table und Model, um die Abfragetabelle anzugeben. Ersteres empfängt einen String-Tabellennamen, Letzteres ein Entity-Modell.

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

TIP

Wenn das übergebene Zeigerelement ein Entity-Modell enthält (z.B. ein Strukturzeiger oder Zeiger auf ein Struktur-Slice), müssen Sie nicht manuell angeben, welche Tabelle abgefragt werden soll. Diese Regel gilt für alle CRUD-Operationen.

Take

Die Take-Methode ähnelt First, mit dem Unterschied, dass sie nicht nach Primärschlüssel sortiert.

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

Die Pluck-Methode wird für Batch-Abfragen einer einzelnen Spalte einer Tabelle verwendet. Die Ergebnisse können in einem Slice eines beliebigen Typs gesammelt werden, nicht unbedingt des Entity-Typs.

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

Beispiel: Sammeln aller Adressen in einem String-Slice:

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)

Dies ist äquivalent zu:

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

Count

Die Count-Methode zählt die Anzahl der Entity-Datensätze:

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

Beispiel:

go
var count int64

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

Find

Für Batch-Abfragen wird am häufigsten Find verwendet:

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

Sie findet alle Datensätze, die den Bedingungen entsprechen:

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

Select

Standardmäßig fragt gorm alle Felder ab. Mit der Select-Methode können Sie Felder angeben:

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

Beispiel:

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

Äquivalent zu:

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

Mit der Omit-Methode können Sie Felder ignorieren:

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

Beispiel:

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)

Die durch Select und Omit ausgewählten oder ignorierten Felder wirken sich auf Create, Update und Query aus.

Where

Für bedingte Abfragen wird die Where-Methode verwendet:

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

Beispiel:

go
var p Person

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

Bei verketteten Operationen erzeugen mehrere Where mehrere AND-Anweisungen:

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)

Oder verwenden Sie Or für OR-Anweisungen:

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)

Ebenso gibt es Not:

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)

Für IN-Bedingungen können Sie direkt Slices in Where übergeben:

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

Für Multi-Spalten-IN-Bedingungen verwenden Sie [][]any:

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 unterstützt WHERE-Gruppierung:

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

Für Sortierung wird Order verwendet:

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

Beispiel:

go
var ps []Person

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

Kann auch mehrfach aufgerufen werden:

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

Limit

Limit und Offset werden oft für Paginierung verwendet:

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

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

Beispiel für Paginierung:

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

Group und Having werden für Gruppierungen verwendet:

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

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

Beispiel:

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

Distinct wird für Deduplizierung verwendet:

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

Beispiel:

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

Unterabfragen

Unterabfragen sind verschachtelte Abfragen. Beispiel: Alle Personen abfragen, deren id größer als der Durchschnitt ist:

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

FROM-Unterabfrage:

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)

Sperren

Gorm verwendet die clause.Locking-Klausel für Sperr-Unterstützung:

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)

Iteration

Mit der Rows-Methode erhalten Sie einen Iterator:

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

Durch Iterieren und Verwendung von ScanRows können Sie jede Zeile in eine Struktur scannen:

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

Update

save

Wie bei Create erwähnt, kann Save auch zum Aktualisieren verwendet werden. Es aktualisiert alle Felder, auch wenn einige Strukturfelder Nullwerte sind. Wenn der Primärschlüssel nicht gefunden wird, wird eingefügt.

go
var p Person

db.First(&p)

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

Es fügt alle Felder außer dem Primärschlüssel zur SET-Anweisung hinzu.

update

In den meisten Fällen wird empfohlen, die Update-Methode zu verwenden:

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

Sie aktualisiert hauptsächlich einzelne Spalten:

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

Updates aktualisiert mehrere Spalten und akzeptiert Strukturen und Maps als Parameter. Nullwerte in Strukturen werden ignoriert, in Maps nicht.

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

Beispiel:

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

SQL-Ausdrücke

Manchmal müssen Felder inkrementiert oder dekrementiert werden. Normalerweise würde man zuerst abfragen, dann berechnen und aktualisieren, oder SQL-Ausdrücke verwenden:

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

Beispiel:

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

Delete

In gorm wird die Delete-Methode zum Löschen von Datensätzen verwendet. Sie kann Entity-Strukturen oder Bedingungen empfangen:

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

Beispiel mit Struktur:

go
var p Person

db.First(&p)

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

Oder:

go
var p Person

db.First(&p)

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

Oder mit Bedingung:

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

Kurzform:

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)

Batch-Löschan mit 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})

Soft Delete

Wenn Ihr Entity-Modell Soft Delete verwendet, wird beim Löschen standardmäßig ein Update durchgeführt. Für dauerhaftes Löschen verwenden Sie Unscope:

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

Assoziationsdefinition

Gorm bietet Möglichkeiten zur Interaktion mit Tabellenassoziationen. Durch Einbettung von Strukturen und Feldern werden Assoziationen zwischen Strukturen definiert.

1:1

Eine 1:1-Beziehung ist am einfachsten. Normalerweise hat eine Person nur eine Mutter:

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
}

Die Person-Struktur referenziert den Mom-Typ durch Einbettung. Person.MomId ist das Referenzfeld, der Primärschlüssel Mom.Id ist das referenzierte Feld. Damit ist die 1:1-Beziehung definiert.

TIP

Für Fremdschlüsselfelder wird empfohlen, Typen aus dem sql-Paket zu verwenden, da Fremdschlüssel standardmäßig NULL sein können. Bei Verwendung von Create mit normalen Typen würde auch der Nullwert 0 erstellt, was nicht erlaubt sein sollte.

1:n

Fügen wir eine Schulstruktur hinzu. Schule und Studenten haben eine 1:n-Beziehung: Eine Schule hat viele Studenten, aber ein Student kann nur eine Schule besuchen.

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 ist vom Typ []person, was bedeutet, dass mehrere Studenten vorhanden sein können. Person muss einen Fremdschlüssel mit der Referenz auf School enthalten, also Person.SchoolId.

n:m

Eine Person kann viele Häuser haben, und ein Haus kann von vielen Personen bewohnt werden - das ist eine n:m-Beziehung.

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 und House halten gegenseitig Slices des anderen Typs, was eine n:m-Beziehung darstellt. N:m-Beziehungen erfordern normalerweise eine Verbindungstabelle, die durch many2many angegeben wird. Die Fremdschlüssel der Verbindungstabelle müssen korrekt angegeben werden.

Nach dem Erstellen der Strukturen lassen Sie gorm automatisch migrieren:

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

Beachten Sie die Reihenfolge der Erstellung von referenzierenden und referenzierten Tabellen.

Assoziationsoperationen

Nach dem Erstellen der drei Assoziationstypen folgt nun die Verwendung für CRUD. Hauptsächlich wird die Association-Methode verwendet:

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

Sie empfängt einen Assoziationsparameter, dessen Wert der Feldname des referenzierten Typs in der eingebetteten Referenzstruktur sein sollte.

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

Beispiel: Assoziationsabfrage der Mutter einer Person. Der Parameter von Association ist Mom, also der Feldname Person.Mom.

Assoziation erstellen

go
// Daten definieren
jenny := Mom{
    Name: "jenny",
}

mit := School{
    Name:    "MIT",
    Persons: nil,
}

h1 := House{
    Id:      0,
    Name:    "h1",
    Persons: nil,
}

h2 := House{
    Name:    "h2",
    Persons: nil,
}

jack := Person{
    Name:    "jack",
    Address: "usa",
    Age:     18,
}

mike := Person{
    Name:    "mike",
    Address: "uk",
    Age:     20,
}

// INSERT INTO `people` (`name`,`address`,`age`,`mom_id`,`school_id`) VALUES ('jack','usa',18,NULL,NULL)
db.Create(&jack)
// INSERT INTO `schools` (`name`) VALUES ('MIT')
db.Create(&mit)

// Person-Mom Assoziation hinzufügen, 1:1
// INSERT INTO `moms` (`name`) VALUES ('jenny') ON DUPLICATE KEY UPDATE `id`=`id`
// UPDATE `people` SET `mom_id`=1 WHERE `id` = 1
db.Model(&jack).Association("Mom").Append(&jenny)

// School-Person Assoziation hinzufügen, 1:n
// INSERT INTO `people` (`name`,`address`,`age`,`mom_id`,`school_id`,`id`) VALUES ('jack','usa',18,1,1,1),('mike','uk',20,NULL,1,DEFAULT) ON DUPLICATE KEY UPDATE `school_id`=VALUES(`school_id`)
db.Model(&mit).Association("Persons").Append([]Person{jack, mike})

// Person-Houses Assoziation hinzufügen, n:m
// 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})

Wenn keine der Datensätze existiert, erstellt gorm bei der Assoziationserstellung zuerst die Datensätze und dann die Assoziation.

Assoziation abfragen

Beispiel für Assoziationsabfragen:

go
// 1:1 Assoziationsabfrage
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)

// 1:n Assoziationsabfrage
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)

// n:m Assoziationsabfrage
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)

Die Assoziationsabfrage sucht basierend auf vorhandenen Daten in der Referenztabelle nach übereinstimmenden Datensätzen. Bei n:m-Beziehungen führt gorm automatisch Tabellen-Joins durch.

Assoziation aktualisieren

Beispiel für Assoziationsaktualisierung:

go
// 1:1 Assoziationsaktualisierung
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)

// 1:n Assoziationsaktualisierung

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)

// n:m Assoziationsaktualisierung

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

Bei der Assoziationsaktualisierung versucht gorm, nicht existierende referenzierte und referenzierende Daten zu erstellen.

Assoziation löschen

Beispiel für Assoziationslöschung:

go
// 1:1 Assoziationslöschung
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)

// 1:n Assoziationslöschung

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)

// n:m Assoziationslöschung
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)

Bei der Assoziationslöschung wird nur die Referenzbeziehung gelöscht, nicht die Entity-Datensätze. Mit der Clear-Methode kann die Assoziation direkt geleert werden:

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

Um die entsprechenden Entity-Datensätze zu löschen, können Sie nach der Association-Operation Unscoped hinzufügen (betrifft nicht many2many):

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

Für 1:n und n:m kann Select verwendet werden:

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

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

Preloading

Preloading wird zum Abfragen von Assoziationsdaten verwendet. Für Entities mit Assoziationen werden die referenzierten Entities vorab geladen. Die zuvor erwähnte Assoziationsabfrage fragt die Assoziationsbeziehung ab, während Preloading direkt die Entity-Datensätze abfragt, einschließlich aller Assoziationen.

Beispiel:

go
var users []Person

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

Dies ist eine 1:1-Assoziationsabfrage. Die Ausgabe zeigt, dass die assoziierte Mom ebenfalls abgefragt wurde.

Sie können auch clause.Associations verwenden, um alle Assoziationen zu laden (außer verschachtelten):

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

Beispiel für verschachteltes Preloading - Abfrage aller Schulen mit allen Studenten und deren Müttern und Häusern:

go
var schools []School

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

Transaktionen

Gorm verwendet standardmäßig Transaktionen. Jede fehlgeschlagene Insert- oder Update-Operation wird zurückgerollt. Dies kann in der Verbindungskonfiguration deaktiviert werden, was die Performance um etwa 30% verbessert.

Automatisch

Closure-Transaktion mit der Transaction-Methode. Wenn die Closure-Funktion nicht nil zurückgibt, wird automatisch zurückgerollt:

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

Beispiel - Operationen in der Closure sollten den Parameter tx verwenden, nicht das äußere db:

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

Manuell

Manuelle Transaktionen werden empfohlen, da Sie selbst kontrollieren, wann zurückgerollt oder committet wird:

go
// Begin startet die Transaktion
func (db *DB) Begin(opts ...*sql.TxOptions) *DB

// Rollback rollt die Transaktion zurück
func (db *DB) Rollback() *DB

// Commit committet die Transaktion
func (db *DB) Commit() *DB

Beispiel:

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

Mit Save Point:

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

Zusammenfassung

Wenn Sie alle oben genannten Inhalte gelesen und den Code geschrieben haben, können Sie gorm für Datenbank-CRUD-Operationen verwenden. Gorm hat neben diesen Operationen viele weitere Funktionen. Weitere Details finden Sie in der offiziellen Dokumentation.

Golang by www.golangdev.cn edit