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
$ go get -u gorm.io/gormVerbindung
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.
$ go get -u gorm.io/driver/mysqlVerwenden Sie dann DSN (Data Source Name), um sich mit der Datenbank zu verbinden. Die Treiberbibliothek analysiert den DSN automatisch in die entsprechende Konfiguration.
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
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.
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})Im Folgenden einige einfache Erklärungen, die Sie je nach Bedarf konfigurieren können.
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.
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.
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.
type Tabler interface {
TableName() string
}In der implementierten Methode wird der String person zurückgegeben. Bei der Datenbankmigration erstellt gorm eine Tabelle namens person.
type Person struct {
Id uint `gorm:"column:ID;"`
Name string `gorm:"column:Name;"`
Address string
Mom string
Dad string
}
func (p Person) TableName() string {
return "person"
}Für die Namensstrategie können Sie auch bei der Verbindungserstellung Ihre eigene Strategie-Implementierung übergeben, um benutzerdefinierte Effekte zu erzielen.
Zeit-Tracking
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().
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:
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:
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.
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.
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:
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:
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:
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:
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:
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.
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:
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:
// 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-Name | Beschreibung |
|---|---|
column | Gibt den DB-Spaltennamen an |
type | Spaltendatentyp, 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 |
serializer | Gibt den Serialisierer an, um Daten zu serialisieren/deserialisieren, z.B.: serializer:json/gob/unixtime |
size | Definiert die Größe oder Länge des Spaltendatentyps, z.B. size: 256 |
primaryKey | Definiert die Spalte als Primärschlüssel |
unique | Definiert die Spalte als eindeutigen Schlüssel |
default | Definiert den Standardwert der Spalte |
precision | Gibt die Genauigkeit der Spalte an |
scale | Gibt die Spaltengröße an |
not null | Gibt an, dass die Spalte NOT NULL ist |
autoIncrement | Gibt an, dass die Spalte automatisch inkrementiert wird |
autoIncrementIncrement | Automatische Schrittweite, steuert den Abstand zwischen aufeinanderfolgenden Datensätzen |
embedded | Eingebettetes Feld |
embeddedPrefix | Präfix für Spaltennamen eingebetteter Felder |
autoCreateTime | Verfolgt 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 |
autoUpdateTime | Verfolgt 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 |
index | Erstellt einen Index basierend auf Parametern, mehrere Felder mit demselben Namen erstellen einen zusammengesetzten Index, siehe Index für Details |
uniqueIndex | Wie index, erstellt aber einen eindeutigen Index |
check | Erstellt 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 |
comment | Fügt bei Migration einen Kommentar für das Feld hinzu |
foreignKey | Gibt die Spalte des aktuellen Modells als Fremdschlüssel der Verbindungstabelle an |
references | Gibt den Spaltennamen der Referenztabelle an, der auf den Fremdschlüssel der Verbindungstabelle abgebildet wird |
polymorphic | Gibt den polymorphen Typ an, z.B. den Modellnamen |
polymorphicValue | Gibt den polymorphen Wert an, Standard Tabellenname |
many2many | Gibt den Namen der Verbindungstabelle an |
joinForeignKey | Gibt den Fremdschlüsselspaltennamen der Verbindungstabelle an, der auf die aktuelle Tabelle abgebildet wird |
joinReferences | Gibt den Fremdschlüsselspaltennamen der Verbindungstabelle an, der auf die Referenztabelle abgebildet wird |
constraint | Beziehungs-Constraint, z.B.: OnUpdate, OnDelete |
Migration
Die AutoMigrate-Methode hilft bei der automatischen Migration. Sie erstellt Tabellen, Constraints, Indizes, Fremdschlüssel usw.
func (db *DB) AutoMigrate(dst ...interface{}) errorBeispiel:
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:
func (db *DB) Migrator() MigratorEs unterstützt folgende Interface-Methoden:
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:
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:
func (db *DB) Create(value interface{}) (tx *DB)Gegeben sei folgende Struktur:
type Person struct {
Id uint `gorm:"primaryKey;"`
Name string
}Erstellen eines Datensatzes:
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.RowsAffectedNach 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:
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.
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.
func (db *DB) Save(value interface{}) (tx *DB)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:
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:
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "name"}},
DoNothing: true,
}).Create(&p)Oder alle Felder aktualisieren:
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:
func (db *DB) First(dest interface{}, conds ...interface{}) (tx *DB)Sie findet den ersten Datensatz nach Primärschlüssel aufsteigend, zum Beispiel:
var person Person
result := db.First(&person)
err := result.Error
affected := result.RowsAffectedDer 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.
func (db *DB) Take(dest interface{}, conds ...interface{}) (tx *DB)var person Person
result := db.Take(&person)
err := result.Error
affected := result.RowsAffectedPluck
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.
func (db *DB) Pluck(column string, dest interface{}) (tx *DB)Beispiel: Sammeln aller Adressen in einem String-Slice:
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:
db.Select("address").Where("name IN ?", []string{"jack", "lili"}).Find(&adds)Count
Die Count-Methode zählt die Anzahl der Entity-Datensätze:
func (db *DB) Count(count *int64) (tx *DB)Beispiel:
var count int64
// SELECT count(*) FROM `person`
db.Model(Person{}).Count(&count)Find
Für Batch-Abfragen wird am häufigsten Find verwendet:
func (db *DB) Find(dest interface{}, conds ...interface{}) (tx *DB)Sie findet alle Datensätze, die den Bedingungen entsprechen:
// 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:
func (db *DB) Select(query interface{}, args ...interface{}) (tx *DB)Beispiel:
// SELECT `address`,`name` FROM `person` ORDER BY `person`.`id` LIMIT 1
db.Select("address", "name").First(&p)Äquivalent zu:
db.Select([]string{"address", "name"}).First(&p)Mit der Omit-Methode können Sie Felder ignorieren:
func (db *DB) Omit(columns ...string) (tx *DB)Beispiel:
// 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:
func (db *DB) Where(query interface{}, args ...interface{}) (tx *DB)Beispiel:
var p Person
db.Where("id = ?", 1).First(&p)Bei verketteten Operationen erzeugen mehrere Where mehrere AND-Anweisungen:
// 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:
func (db *DB) Or(query interface{}, args ...interface{}) (tx *DB)// SELECT * FROM `person` WHERE id = 1 OR name = 'jack' AND address = 'usa' ORDER BY `person`.`id` LIMIT 1
db.Where("id = ?", 1).
Or("name = ?", "jack").
Where("address = ?", "usa").
First(&p)Ebenso gibt es Not:
func (db *DB) Not(query interface{}, args ...interface{}) (tx *DB)// SELECT * FROM `person` WHERE id = 1 OR name = 'jack' AND NOT name = 'mike' AND address = 'usa' ORDER BY `person`.`id` LIMIT 1
db.Where("id = ?", 1).
Or("name = ?", "jack").
Not("name = ?", "mike").
Where("address = ?", "usa").
First(&p)Für IN-Bedingungen können Sie direkt Slices in Where übergeben:
db.Where("address IN ?", []string{"cn", "us"}).Find(&ps)Für Multi-Spalten-IN-Bedingungen verwenden Sie [][]any:
// 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:
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:
func (db *DB) Order(value interface{}) (tx *DB)Beispiel:
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:
// 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:
func (db *DB) Limit(limit int) (tx *DB)
func (db *DB) Offset(offset int) (tx *DB)Beispiel für Paginierung:
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:
func (db *DB) Group(name string) (tx *DB)
func (db *DB) Having(query interface{}, args ...interface{}) (tx *DB)Beispiel:
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:
func (db *DB) Distinct(args ...interface{}) (tx *DB)Beispiel:
// 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:
// SELECT * FROM `person` WHERE id > (SELECT AVG(id) FROM `person`
db.Where("id > (?)", db.Model(Person{}).Select("AVG(id)")).Find(&ps)FROM-Unterabfrage:
// 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:
// 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:
func (db *DB) Rows() (*sql.Rows, error)Durch Iterieren und Verwendung von ScanRows können Sie jede Zeile in eine Struktur scannen:
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.
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:
func (db *DB) Update(column string, value interface{}) (tx *DB)Sie aktualisiert hauptsächlich einzelne Spalten:
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.
func (db *DB) Updates(values interface{}) (tx *DB)Beispiel:
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:
func Expr(expr string, args ...interface{}) clause.ExprBeispiel:
// 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:
func (db *DB) Delete(value interface{}, conds ...interface{}) (tx *DB)Beispiel mit Struktur:
var p Person
db.First(&p)
// DELETE FROM `person` WHERE `person`.`id` = 2
db.Delete(&p)Oder:
var p Person
db.First(&p)
// DELETE FROM `person` WHERE `person`.`id` = 2
db.Model(p).Delete(nil)Oder mit Bedingung:
// DELETE FROM `person` WHERE id = 2
db.Model(Person{}).Where("id = ?", p.Id).Delete(nil)Kurzform:
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:
// 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:
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:
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.
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.
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:
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:
func (db *DB) Association(column string) *AssociationSie empfängt einen Assoziationsparameter, dessen Wert der Feldname des referenzierten Typs in der eingebetteten Referenzstruktur sein sollte.
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
// 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:
// 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:
// 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:
// 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:
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):
db.Model(&jack).Association("Houses").Unscoped().Delete(&houses)Für 1:n und n:m kann Select verwendet werden:
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:
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):
db.Preload(clause.Associations).Find(&users)Beispiel für verschachteltes Preloading - Abfrage aller Schulen mit allen Studenten und deren Müttern und Häusern:
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:
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:
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:
// 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() *DBBeispiel:
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:
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.
