Skip to content

Gorm Library ORM Database

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

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

Di komunitas go untuk bagian interaksi database ada dua kubu satu kubu lebih menyukai library sederhana seperti sqlx fungsinya tidak begitu kuat tetapi kita dapat mengontrol sql setiap saat performa optimal. Kubu lain menyukai ORM yang lahir untuk efisiensi pengembangan dapat menghemat banyak masalah yang tidak perlu dalam proses pengembangan. Dan ketika berbicara tentang ORM di komunitas bahasa go pasti tidak bisa dilewatkan gorm ini adalah ORM yang sangat established yang serupa adalah xorm yang relatif lebih muda ent dll. Artikel ini membahas tentang konten gorm artikel ini hanya menjelaskan konten dasar入门 sebagai pengantar untuk memicu diskusi lebih lanjut ingin memahami detail lebih dalam dapat membaca dokumentasi resmi dokumentasi bahasa Indonesianya sudah cukup lengkap dan penulis juga merupakan salah satu penerjemah dokumentasi gorm.

Fitur

  • ORM fungsional lengkap
  • Asosiasi (memiliki satu memiliki banyak milik banyak-ke-banyak polimorfik inheritance tabel tunggal)
  • Metode hook di Create Save Update Delete Find
  • Mendukung Preload Joins
  • Transaksi transaksi bersarang Save Point Rollback To Saved Point
  • Context mode prepared statement mode DryRun
  • Batch insert FindInBatches Find/Create dengan Map CRUD menggunakan ekspresi SQL Context Valuer
  • SQL Builder Upsert lock Optimizer/Index/Comment Hint named parameter subquery
  • Composite primary key index constraint
  • Auto migration
  • Logger kustom
  • API plugin yang dapat diperluas secara fleksibel: Database Resolver (multi database read/write separation) Prometheus...
  • Setiap fitur telah diuji dengan ketat
  • Ramah developer

gorm tentu juga memiliki beberapa kelemahan misalnya hampir semua parameter method adalah tipe interface kosong jika tidak melihat dokumentasi mungkin tidak tahu apa yang harus dimasukkan kadang dapat mengirim struktur kadang dapat mengirim string kadang dapat mengirim map kadang dapat mengirim slice semantik agak kabur dan banyak situasi masih perlu menulis SQL sendiri.

Sebagai alternatif ada dua orm yang dapat dicoba yang pertama adalah aorm baru saja open source tidak perlu menulis nama field tabel sendiri sebagian besar operasi chain-based berdasarkan refleksi karena jumlah star tidak banyak dapat menunggu sebentar. Yang kedua adalah ent adalah orm open source dari facebook juga mendukung operasi chain dan sebagian besar situasi tidak perlu menulis SQL sendiri desainnya berdasarkan graph (graph dalam struktur data) implementasinya berdasarkan code generation bukan refleksi (cukup setuju dengan ini) tetapi dokumentasinya bahasa Inggris penuh ada ambang batas tertentu.

Instalasi

Instal library gorm

sh
$ go get -u gorm.io/gorm

Koneksi

gorm saat ini mendukung beberapa database berikut

  • 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 kompatibel dengan protokol mysql
  • ClickHouse: "gorm.io/driver/clickhouse"

Selain itu ada beberapa driver database lain yang disediakan oleh developer pihak ketiga misalnya driver oracle CengSin/oracle. Artikel selanjutnya akan menggunakan MySQL untuk demonstrasi database apa yang digunakan perlu menginstal driver yang sesuai di sini menginstal driver gorm Mysql.

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

Kemudian terhubung ke database menggunakan dsn (data source name) driver library akan mengurai dsn ke konfigurasi yang sesuai

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

Atau manual mengirim konfigurasi

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

Kedua metode setara lihat kebiasaan penggunaan.

Konfigurasi Koneksi

Dengan mengirim struktur konfigurasi gorm.Config kita dapat mengontrol beberapa perilaku gorm

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

Berikut adalah beberapa penjelasan sederhana saat menggunakan dapat dikonfigurasi sesuai kebutuhan.

go
type Config struct {
  // Level logging minimum
  SkipDefaultTransaction bool
  // Mode development terutama mempengaruhi stack trace
  Development bool
  // Pelacakan caller
  DisableCaller bool
  // Stack trace
  DisableStacktrace bool
  // Sampling hanya mencatat sebagian log yang lebih representatif
  Sampling *SamplingConfig
  // Encoding dibagi menjadi dua mode json dan console
  Encoding string
  // Konfigurasi encoding terutama beberapa konfigurasi format output
  EncoderConfig zapcore.EncoderConfig
  // Path output file logging
  OutputPaths []string
  // Path output file error
  ErrorOutputPaths []string
  // Menambahkan beberapa konten output default ke logging
  InitialFields map[string]interface{}
}

Model

Dalam gorm model sesuai dengan tabel database biasanya ditampilkan dalam bentuk struktur misalnya struktur berikut.

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

Struktur internal dapat terdiri dari tipe data dasar dan tipe yang mengimplementasikan interface sql.Scanner dan sql.Valuer. Secara default tabel yang dipetakan oleh struktur Person adalah persons yang merupakan gaya plural snake_case dipisahkan dengan underscore. Nama kolom juga dalam gaya snake_case misalnya Id sesuai dengan nama kolom id gorm juga menyediakan beberapa cara untuk mengkonfigurasinya.

Menentukan Nama Kolom

Melalui tag struktur kita dapat menentukan nama kolom untuk field struktur sehingga saat entity mapping gorm akan menggunakan nama kolom yang ditentukan.

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

Menentukan Nama Tabel

Dengan mengimplementasikan interface Table dapat menentukan nama tabel hanya memiliki satu method yaitu mengembalikan nama tabel.

go
type Tabler interface {
  TableName() string
}

Dalam method yang diimplementasikan mengembalikan string person saat migrasi database gorm akan membuat tabel bernama 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"
}

Untuk kebijakan penamaan juga dapat mengirimkan implementasi kebijakan sendiri saat membuat koneksi untuk mencapai efek kustom.

Pelacakan Waktu

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

Ketika berisi field CreatedAt atau UpdatedAt saat membuat atau memperbarui record jika nilainya nol gorm akan otomatis menggunakan time.Now() untuk mengatur waktu.

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

Maka saat Create dieksekusi setara dengan SQL berikut

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

Dalam situasi aktual jika ada kebutuhan pelacakan waktu lebih direkomendasikan menyimpan timestamp di backend dalam kasus lintas zona waktu penanganan lebih sederhana.

Model

gorm menyediakan struktur Model preset yang berisi primary key ID dan dua field pelacakan waktu dan satu field soft delete.

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

Saat menggunakan cukup embed ke dalam model entitas.

go
type Order struct {
  gorm.Model
  Name string
}

Dengan demikian akan memiliki semua karakteristik gorm.Model.

Primary Key

Secara default field bernama Id adalah primary key menggunakan tag struktur dapat menentukan field primary key

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

  CreatedAt sql.NullTime
  UpdatedAt sql.NullTime
}

Beberapa field membentuk composite primary key

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

Melalui tag struktur index dapat menentukan index kolom

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

Dalam struktur di atas建立了 unique index pada field Address. Dua field menggunakan index dengan nama yang sama akan membuat composite 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;"`
}

Foreign Key

Dalam struktur mendefinisikan relasi foreign key dilakukan dengan cara embed struktur misalnya

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

Dalam contoh struktur Person memiliki dua foreign key masing-masing mereferensi primary key dari struktur Dad dan Mom default adalah primary key. Person untuk Dad dan Mom adalah relasi one-to-one satu orang hanya dapat memiliki satu ayah dan ibu. Dad dan Mom untuk Person adalah relasi one-to-many karena ayah dan ibu dapat memiliki banyak anak.

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

Fungsi embed struktur adalah untuk memudahkan menentukan foreign key dan referensi secara default format nama field foreign key adalah NamaTipeDireferensi+Id misalnya MomId. Secara default adalah primary key yang direferensikan melalui tag struktur dapat menentukan untuk mereferensikan field tertentu

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

Di mana constraint:OnUpdate:CASCADE,OnDelete:SET NULL; adalah foreign key constraint yang didefinisikan.

Hook

Model entitas dapat menyesuaikan hook

  • Create
  • Update
  • Delete
  • Query

Interface yang sesuai masing-masing sebagai berikut

go
// Dipicu sebelum create
type BeforeCreateInterface interface {
    BeforeCreate(*gorm.DB) error
}

// Dipicu setelah create
type AfterCreateInterface interface {
    AfterCreate(*gorm.DB) error
}

// Dipicu sebelum update
type BeforeUpdateInterface interface {
    BeforeUpdate(*gorm.DB) error
}

// Dipicu setelah update
type AfterUpdateInterface interface {
    AfterUpdate(*gorm.DB) error
}

// Dipicu sebelum save
type BeforeSaveInterface interface {
    BeforeSave(*gorm.DB) error
}

// Dipicu setelah save
type AfterSaveInterface interface {
    AfterSave(*gorm.DB) error
}

// Dipicu sebelum delete
type BeforeDeleteInterface interface {
    BeforeDelete(*gorm.DB) error
}

// Dipicu setelah delete
type AfterDeleteInterface interface {
    AfterDelete(*gorm.DB) error
}

// Dipicu setelah find
type AfterFindInterface interface {
    AfterFind(*gorm.DB) error
}

Struktur dapat menyesuaikan beberapa perilaku dengan mengimplementasikan interface ini.

Tag

Berikut adalah beberapa tag yang didukung gorm

Nama TagDeskripsi
columnMenentukan nama kolom db
typeTipe data kolom direkomendasikan menggunakan tipe universal dengan kompatibilitas baik misalnya semua database mendukung bool int uint float string time bytes dan dapat digunakan dengan tag lain seperti not null size autoIncrement... Seperti varbinary(8) menentukan tipe data database juga didukung. Saat menggunakan tipe data database yang ditentukan harus berupa tipe data database lengkap seperti MEDIUMINT UNSIGNED not NULL AUTO_INCREMENT
serializerMenentukan serializer untuk serialisasi atau deserialisasi data ke database misalnya serializer:json/gob/unixtime
sizeMenentukan ukuran atau panjang tipe data kolom misalnya size: 256
primaryKeyMenentukan kolom sebagai primary key
uniqueMenentukan kolom sebagai unique key
defaultMenentukan nilai default kolom
precisionMenentukan presisi kolom
scaleMenentukan skala kolom
not nullMenentukan kolom sebagai NOT NULL
autoIncrementMenentukan kolom sebagai auto increment
autoIncrementIncrementAuto increment increment mengontrol interval antara record berturut-turut
embeddedField embedded
embeddedPrefixPrefix nama kolom field embedded
autoCreateTimeMelacak waktu saat create untuk field int akan melacak timestamp detik dapat menggunakan nano/milli untuk melacak timestamp nanodetik milidetik misalnya autoCreateTime:nano
autoUpdateTimeMelacak waktu saat create/update untuk field int akan melacak timestamp detik dapat menggunakan nano/milli untuk melacak timestamp nanodetik milidetik misalnya autoUpdateTime:milli
indexMembuat index sesuai parameter beberapa field menggunakan nama yang sama akan membuat composite index lihat Index untuk detail
uniqueIndexSama dengan index tetapi membuat unique index
checkMembuat check constraint misalnya check:age > 13 lihat Constraint untuk detail
<-Menentukan permission write field <-:create hanya create <-:update hanya update <-:false tidak ada permission write <- permission create dan update
->Menentukan permission read field ->:false tidak ada permission read
-Mengabaikan field - berarti tidak ada read write -:migration tidak ada permission migration -:all tidak ada read write migration permission
commentMenambahkan komentar untuk field saat migration
foreignKeyMenentukan kolom model saat ini sebagai foreign key tabel join
referencesMenentukan nama kolom tabel yang direferensikan yang akan dipetakan sebagai foreign key tabel join
polymorphicMenentukan tipe polimorfik misalnya nama model
polymorphicValueMenentukan nilai polimorfik default nama tabel
many2manyMenentukan nama tabel join
joinForeignKeyMenentukan nama kolom foreign key tabel join yang akan dipetakan ke tabel saat ini
joinReferencesMenentukan nama kolom foreign key tabel join yang akan dipetakan ke tabel yang direferensikan
constraintConstraint relasi misalnya OnUpdate OnDelete

Migration

Method AutoMigrate akan membantu melakukan auto migration akan membuat tabel constraint index foreign key dll.

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

Misalnya

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

Atau juga dapat manual mengoperasikan melalui method Migrator mengakses interface Migrator

go
func (db *DB) Migrator() Migrator

Mendukung method interface berikut

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

Daftar method melibatkan database tabel kolom view index constraint beberapa dimensi bagi pengguna yang perlu kustom dapat melakukan operasi yang lebih terperinci.

Menentukan Komentar Tabel

Saat migration jika ingin menambahkan komentar tabel dapat diatur dengan metode berikut

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

Perlu dicatat jika menggunakan method AutoMigrate() untuk migration dan struktur memiliki relasi referensi gorm akan melakukan rekursi membuat tabel yang direferensikan terlebih dahulu ini akan menyebabkan tabel yang direferensikan dan tabel yang mereferensikan komentarnya sama jadi direkomendasikan menggunakan method CreateTable untuk membuat.

TIP

Saat membuat tabel method CreateTable perlu memastikan tabel yang direferensikan dibuat lebih dulu daripada tabel yang mereferensikan jika tidak akan error sedangkan method AutoMigrate tidak perlu karena akan mengikuti relasi referensi secara rekursif.

Create

Create

Saat membuat record baru sebagian besar situasi akan menggunakan method Create

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

Ada struktur berikut

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

Membuat satu record

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

// Harus mengirim referensi
db = db.Create(&user)

// Error yang terjadi selama eksekusi
err = db.Error
// Jumlah yang dibuat
affected := db.RowsAffected

Setelah create selesai gorm akan menulis primary key ke struktur user jadi ini adalah alasan mengapa harus mengirim pointer. Jika mengirim slice akan batch create

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

db = db.Create(&user)

Sama gorm juga akan menulis primary key ke slice. Ketika data terlalu besar juga dapat menggunakan method CreateInBatches untuk create secara batch karena SQL yang dihasilkan INSERT INTO table VALUES (),() akan menjadi sangat panjang setiap database memiliki batasan panjang SQL jadi saat diperlukan dapat memilih create secara batch.

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

Selain itu method Save juga dapat membuat record fungsinya adalah ketika primary key cocok akan update record jika tidak akan insert.

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

db = db.Save(&user)

Upsert

Method Save hanya dapat mencocokkan primary key kita dapat membangun Clause untuk menyelesaikan upsert yang lebih kustom. Misalnya kode berikut

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

Fungsinya adalah ketika field name konflik akan update nilai field address jika tidak konflik akan membuat record baru. Juga dapat melakukan apa-apa saat konflik

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

Atau langsung update semua field

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

Sebelum menggunakan upsert ingat untuk menambahkan index pada field yang konflik.

Query

First

gorm untuk query menyediakan banyak method yang tersedia yang pertama adalah method First

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

Fungsinya adalah mencari record pertama sesuai primary key ascending misalnya

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

Mengirim pointer dest untuk memudahkan gorm memetakan data yang diquery ke struktur.

Atau menggunakan method Table dan Model dapat menentukan tabel query yang pertama menerima string nama tabel yang kedua menerima model entitas.

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

TIP

Jika pointer yang dikirim mengandung model entitas misalnya pointer struktur atau pointer slice struktur maka tidak perlu manual menentukan query tabel mana aturan ini berlaku untuk semua operasi create delete update query.

Take

Method Take mirip dengan First perbedaannya adalah tidak akan mengurutkan berdasarkan primary key.

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

Method Pluck digunakan untuk batch query satu kolom dari tabel hasil query dapat dikumpulkan ke slice tipe yang ditentukan tidak harus slice tipe entitas.

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

Misalnya mengumpulkan alamat semua orang ke slice string

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)

Sebenarnya setara dengan

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

Count

Method Count digunakan untuk menghitung jumlah record entitas

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

Lihat contoh penggunaan

go
var count int64

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

Find

Yang paling umum untuk batch query adalah method Find

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

Akan mencari semua record yang sesuai dengan kondisi yang diberikan

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

Select

gorm secara default query semua field kita dapat menggunakan method Select untuk menentukan field

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

Misalnya

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

Setara dengan

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

Juga dapat menggunakan method Omit untuk mengabaikan field

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

Misalnya

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)

Field yang dipilih atau diabaikan oleh Select dan Omit akan berfungsi saat membuat update query.

Where

Query kondisi akan menggunakan method Where

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

Berikut adalah contoh sederhana

go
var p Person

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

Menggunakan beberapa Where dalam operasi chain akan membangun beberapa statement AND misalnya

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)

Atau menggunakan method Or untuk membangun statement 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)

Ada juga method Not semuanya serupa

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)

Untuk kondisi IN dapat langsung mengirim slice di dalam method Where.

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

Atau kondisi IN multi kolom perlu menggunakan tipe [][]any untuk menampung parameter

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 mendukung penggunaan grouping where yaitu menggabungkan beberapa statement di atas

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

Sorting akan menggunakan method Order

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

Lihat contoh penggunaan

go
var ps []Person

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

Juga dapat memanggil beberapa kali

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

Limit

Method Limit dan Offset sering digunakan untuk query pagination

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

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

Berikut adalah contoh pagination sederhana

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

Method Group dan Having banyak digunakan untuk operasi grouping

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

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

Lihat contoh berikut

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

Method Distinct banyak digunakan untuk deduplikasi

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

Lihat contoh

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

Subquery

Subquery adalah nested query misalnya ingin query semua orang yang nilai id lebih besar dari rata-rata

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

from subquery

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)

Lock

gorm menggunakan clause clause.Locking untuk menyediakan dukungan lock

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)

Iterasi

Melalui method Rows dapat memperoleh iterator

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

Melalui遍历 iterator menggunakan method ScanRows dapat memindai hasil setiap baris ke struktur.

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

Disebutkan saat create method Save juga dapat digunakan untuk update record dan akan update semua field bahkan beberapa field struktur adalah nilai nol tetapi jika primary key tidak cocok akan melakukan insert.

go
var p Person

db.First(&p)

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

Dapat dilihat menambahkan semua field kecuali primary key ke statement SET.

Update

Jadi sebagian besar situasi direkomendasikan menggunakan method Update

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

Utamanya digunakan untuk update single column field

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

Method Updates digunakan untuk update multi column menerima struktur dan map sebagai parameter dan ketika field struktur adalah nilai nol akan mengabaikan field tersebut tetapi di map tidak.

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

Berikut adalah contoh

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

Ekspresi SQL

Kadang-kadang sering perlu melakukan beberapa operasi seperti increment atau decrement pada field umumnya query dulu kemudian kalkulasi lalu update atau menggunakan ekspresi SQL.

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

Lihat contoh berikut

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

Dalam gorm delete record akan menggunakan method Delete dapat langsung mengirim struktur entitas juga dapat mengirim kondisi.

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

Misalnya langsung mengirim struktur

go
var p Person

db.First(&p)

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

Atau

go
var p Person

db.First(&p)

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

Atau menentukan kondisi

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

Juga dapat disederhanakan menjadi

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)

Untuk batch delete adalah mengirim 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

Jika model entitas menggunakan soft delete saat delete secara default melakukan operasi update jika ingin permanent delete dapat menggunakan method Unscoped

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

Definisi Relasi

gorm menyediakan kemampuan interaksi relasi tabel melalui embed struktur dan field untuk mendefinisikan relasi antar struktur.

One-to-One

Relasi one-to-one adalah yang paling sederhana正常情况下 satu orang hanya dapat memiliki satu ibu lihat struktur berikut

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
}

Struktur Person melalui embed struktur Mom实现了对tipe Mom referensi di mana Person.MomId adalah field referensi primary key Mom.Id adalah field yang direferensikan sehingga menyelesaikan relasi one-to-one. Cara kustom foreign key dan referensi dan constraint serta aturan foreign key default telah dibahas di Definisi Foreign Key tidak akan diulang

TIP

Untuk field foreign key direkomendasikan menggunakan tipe yang disediakan package sql karena foreign key secara default dapat NULL saat menggunakan Create untuk membuat record jika menggunakan tipe biasa nilai nol 0 juga akan dibuat foreign key yang tidak ada dibuat jelas tidak diizinkan.

One-to-Many

Berikutnya tambahkan struktur school relasi school dan student adalah one-to-many satu school memiliki banyak student tetapi satu student hanya dapat bersekolah di satu school.

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 adalah tipe []person表示 dapat memiliki banyak student sedangkan Person harus包含 referensi School foreign key yaitu Person.SchoolId.

Many-to-Many

Satu orang dapat memiliki banyak rumah satu rumah juga dapat dihuni banyak orang ini adalah relasi many-to-many.

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 dan House saling memegang slice tipe对方表示 relasi many-to-many relasi many-to-many umumnya perlu membuat connection table melalui many2many untuk menentukan connection table foreign key connection table harus ditentukan dengan benar.

Setelah membuat struktur biarkan gorm auto migrate ke database

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

Perhatikan urutan create tabel yang direferensikan dan tabel yang mereferensikan.

Operasi Relasi

Setelah membuat tiga relasi di atas selanjutnya adalah bagaimana menggunakan relasi untuk create delete update query. Ini terutama akan menggunakan method Association

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

Menerima parameter relasi nilainya harus nama field dari tipe yang direferensikan yang di-embed dalam struktur referensi.

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

Misalnya relasi find ibu seseorang parameter Association adalah Mom yaitu nama field Person.Mom.

Create Relasi

go
// Mendefinisikan data
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)

// Menambahkan relasi Person dengan Mom relasi one-to-one
// 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)

// Menambahkan relasi school dengan Person relasi one-to-many
// 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})

// Menambahkan relasi Person dengan Houses relasi many-to-many
// 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})

Jika semua record tidak ada saat create relasi juga akan membuat record terlebih dahulu kemudian create relasi.

Find Relasi

Berikut mendemonstrasikan bagaimana find relasi.

go
// Find relasi one-to-one
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)

// Find relasi one-to-many
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)

// Find relasi many-to-many
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)

Find relasi akan mencari record yang sesuai di tabel referensi berdasarkan data yang ada untuk relasi many-to-many gorm akan otomatis menyelesaikan proses join tabel.

Update Relasi

Berikut mendemonstrasikan bagaimana update relasi

go
// Update relasi one-to-one
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)

// Update relasi one-to-many

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)

// Update relasi many-to-many

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

Saat update relasi jika data yang direferensikan dan data yang mereferensikan tidak ada gorm akan mencoba membuatnya.

Delete Relasi

Berikut mendemonstrasikan bagaimana delete relasi

go
// Delete relasi one-to-one
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)

// Delete relasi one-to-many

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)

// Delete relasi many-to-many
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)

Delete relasi hanya akan menghapus relasi referensi di antara mereka tidak akan menghapus record entitas. Juga dapat menggunakan method Clear untuk langsung清空 relasi

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

Jika ingin menghapus record entitas yang sesuai dapat menambahkan operasi Unscoped setelah operasi Association (tidak akan mempengaruhi many2many)

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

Untuk one-to-many dan many-to-many dapat menggunakan operasi Select untuk delete record

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

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

Preload

Preload digunakan untuk query data relasi untuk entitas yang memiliki relasi akan memuat entitas yang direferensikan terlebih dahulu. Query relasi yang disebutkan sebelumnya adalah query untuk relasi preload adalah query langsung untuk record entitas termasuk semua relasi. Dari segi syntax query relasi perlu query []Person yang ditentukan terlebih dahulu kemudian berdasarkan []Person untuk query []Mom yang terkait preload dari syntax langsung query []Person dan juga akan memuat semua relasi tetapi sebenarnya SQL yang dieksekusi hampir sama. Lihat contoh berikut

go
var users []Person

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

Ini adalah contoh query relasi one-to-one outputnya

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:[]}]

Dapat dilihat juga query Mom yang terkait tetapi tidak preload relasi school semua struktur School adalah nilai nol. Juga dapat menggunakan clause.Associations untuk表示 preload semua relasi kecuali relasi nested.

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

Berikut adalah contoh preload nested fungsinya adalah query semua student yang terkait dengan semua school dan ibu yang terkait dengan setiap student dan rumah yang dimiliki setiap student dan juga query set pemilik setiap rumah school->student->house->student.

go
var schools []School

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

// Output code logika dapat diabaikan
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()
    }
}

Output adalah

school MIT
person jack
mom jenny
house h1 owner [jack]
house h2 owner [jack]

person mike
mom

Dapat dilihat output ibu setiap student di setiap school dan rumah mereka dan semua pemilik rumah.

Transaksi

gorm default开启 transaksi setiap insert dan update akan rollback setelah gagal dapat ditutup di Konfigurasi Koneksi performa akan meningkat sekitar 30%. Penggunaan transaksi di gorm ada beberapa method berikut diperkenalkan secara singkat.

Otomatis

Closure transaction melalui method Transaction mengirim fungsi closure jika nilai return fungsi tidak nil akan otomatis rollback.

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

Lihat contoh operasi di closure harus menggunakan parameter tx bukan db eksternal.

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

Manual

Lebih direkomendasikan menggunakan manual transaction kita sendiri yang mengontrol kapan rollback kapan commit. Manual transaction akan menggunakan tiga method berikut

go
// Method Begin digunakan untuk memulai transaksi
func (db *DB) Begin(opts ...*sql.TxOptions) *DB

// Method Rollback digunakan untuk rollback transaksi
func (db *DB) Rollback() *DB

// Method Commit digunakan untuk commit transaksi
func (db *DB) Commit() *DB

Lihat contoh setelah memulai transaksi harus menggunakan tx untuk operasi 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()

Dapat menentukan savepoint

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

Kesimpulan

Jika Anda telah membaca semua konten di atas dan mengetik kode maka Anda dapat menggunakan gorm untuk melakukan create delete update query database gorm selain operasi ini juga memiliki banyak fungsi lain lebih banyak detail dapat前往官方文档了解.

Golang by www.golangdev.cn edit