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
$ go get -u gorm.io/gormKoneksi
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.
$ go get -u gorm.io/driver/mysqlKemudian terhubung ke database menggunakan dsn (data source name) driver library akan mengurai dsn ke konfigurasi yang sesuai
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
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
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})Berikut adalah beberapa penjelasan sederhana saat menggunakan dapat dikonfigurasi sesuai kebutuhan.
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.
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.
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.
type Tabler interface {
TableName() string
}Dalam method yang diimplementasikan mengembalikan string person saat migrasi database gorm akan membuat tabel bernama 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"
}Untuk kebijakan penamaan juga dapat mengirimkan implementasi kebijakan sendiri saat membuat koneksi untuk mencapai efek kustom.
Pelacakan Waktu
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.
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
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
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.
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.
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
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
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
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
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
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.
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
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
// 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 Tag | Deskripsi |
|---|---|
column | Menentukan nama kolom db |
type | Tipe 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 |
serializer | Menentukan serializer untuk serialisasi atau deserialisasi data ke database misalnya serializer:json/gob/unixtime |
size | Menentukan ukuran atau panjang tipe data kolom misalnya size: 256 |
primaryKey | Menentukan kolom sebagai primary key |
unique | Menentukan kolom sebagai unique key |
default | Menentukan nilai default kolom |
precision | Menentukan presisi kolom |
scale | Menentukan skala kolom |
not null | Menentukan kolom sebagai NOT NULL |
autoIncrement | Menentukan kolom sebagai auto increment |
autoIncrementIncrement | Auto increment increment mengontrol interval antara record berturut-turut |
embedded | Field embedded |
embeddedPrefix | Prefix nama kolom field embedded |
autoCreateTime | Melacak waktu saat create untuk field int akan melacak timestamp detik dapat menggunakan nano/milli untuk melacak timestamp nanodetik milidetik misalnya autoCreateTime:nano |
autoUpdateTime | Melacak waktu saat create/update untuk field int akan melacak timestamp detik dapat menggunakan nano/milli untuk melacak timestamp nanodetik milidetik misalnya autoUpdateTime:milli |
index | Membuat index sesuai parameter beberapa field menggunakan nama yang sama akan membuat composite index lihat Index untuk detail |
uniqueIndex | Sama dengan index tetapi membuat unique index |
check | Membuat 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 |
comment | Menambahkan komentar untuk field saat migration |
foreignKey | Menentukan kolom model saat ini sebagai foreign key tabel join |
references | Menentukan nama kolom tabel yang direferensikan yang akan dipetakan sebagai foreign key tabel join |
polymorphic | Menentukan tipe polimorfik misalnya nama model |
polymorphicValue | Menentukan nilai polimorfik default nama tabel |
many2many | Menentukan nama tabel join |
joinForeignKey | Menentukan nama kolom foreign key tabel join yang akan dipetakan ke tabel saat ini |
joinReferences | Menentukan nama kolom foreign key tabel join yang akan dipetakan ke tabel yang direferensikan |
constraint | Constraint relasi misalnya OnUpdate OnDelete |
Migration
Method AutoMigrate akan membantu melakukan auto migration akan membuat tabel constraint index foreign key dll.
func (db *DB) AutoMigrate(dst ...interface{}) errorMisalnya
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
func (db *DB) Migrator() MigratorMendukung method interface berikut
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
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
func (db *DB) Create(value interface{}) (tx *DB)Ada struktur berikut
type Person struct {
Id uint `gorm:"primaryKey;"`
Name string
}Membuat satu record
user := Person{
Name: "jack",
}
// Harus mengirim referensi
db = db.Create(&user)
// Error yang terjadi selama eksekusi
err = db.Error
// Jumlah yang dibuat
affected := db.RowsAffectedSetelah create selesai gorm akan menulis primary key ke struktur user jadi ini adalah alasan mengapa harus mengirim pointer. Jika mengirim slice akan batch create
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.
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.
func (db *DB) Save(value interface{}) (tx *DB)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
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
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "name"}},
DoNothing: true,
}).Create(&p)Atau langsung update semua field
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
func (db *DB) First(dest interface{}, conds ...interface{}) (tx *DB)Fungsinya adalah mencari record pertama sesuai primary key ascending misalnya
var person Person
result := db.First(&person)
err := result.Error
affected := result.RowsAffectedMengirim 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.
func (db *DB) Take(dest interface{}, conds ...interface{}) (tx *DB)var person Person
result := db.Take(&person)
err := result.Error
affected := result.RowsAffectedPluck
Method Pluck digunakan untuk batch query satu kolom dari tabel hasil query dapat dikumpulkan ke slice tipe yang ditentukan tidak harus slice tipe entitas.
func (db *DB) Pluck(column string, dest interface{}) (tx *DB)Misalnya mengumpulkan alamat semua orang ke slice string
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
db.Select("address").Where("name IN ?", []string{"jack", "lili"}).Find(&adds)Count
Method Count digunakan untuk menghitung jumlah record entitas
func (db *DB) Count(count *int64) (tx *DB)Lihat contoh penggunaan
var count int64
// SELECT count(*) FROM `person`
db.Model(Person{}).Count(&count)Find
Yang paling umum untuk batch query adalah method Find
func (db *DB) Find(dest interface{}, conds ...interface{}) (tx *DB)Akan mencari semua record yang sesuai dengan kondisi yang diberikan
// SELECT * FROM `person`
var ps []Person
db.Find(&ps)Select
gorm secara default query semua field kita dapat menggunakan method Select untuk menentukan field
func (db *DB) Select(query interface{}, args ...interface{}) (tx *DB)Misalnya
// SELECT `address`,`name` FROM `person` ORDER BY `person`.`id` LIMIT 1
db.Select("address", "name").First(&p)Setara dengan
db.Select([]string{"address", "name"}).First(&p)Juga dapat menggunakan method Omit untuk mengabaikan field
func (db *DB) Omit(columns ...string) (tx *DB)Misalnya
// 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
func (db *DB) Where(query interface{}, args ...interface{}) (tx *DB)Berikut adalah contoh sederhana
var p Person
db.Where("id = ?", 1).First(&p)Menggunakan beberapa Where dalam operasi chain akan membangun beberapa statement AND misalnya
// 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
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)Ada juga method Not semuanya serupa
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)Untuk kondisi IN dapat langsung mengirim slice di dalam method Where.
db.Where("address IN ?", []string{"cn", "us"}).Find(&ps)Atau kondisi IN multi kolom perlu menggunakan tipe [][]any untuk menampung parameter
// 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
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
func (db *DB) Order(value interface{}) (tx *DB)Lihat contoh penggunaan
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
// 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
func (db *DB) Limit(limit int) (tx *DB)
func (db *DB) Offset(offset int) (tx *DB)Berikut adalah contoh pagination sederhana
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
func (db *DB) Group(name string) (tx *DB)
func (db *DB) Having(query interface{}, args ...interface{}) (tx *DB)Lihat contoh berikut
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
func (db *DB) Distinct(args ...interface{}) (tx *DB)Lihat contoh
// 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
// SELECT * FROM `person` WHERE id > (SELECT AVG(id) FROM `person`
db.Where("id > (?)", db.Model(Person{}).Select("AVG(id)")).Find(&ps)from subquery
// 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
// 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
func (db *DB) Rows() (*sql.Rows, error)Melalui遍历 iterator menggunakan method ScanRows dapat memindai hasil setiap baris ke struktur.
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.
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
func (db *DB) Update(column string, value interface{}) (tx *DB)Utamanya digunakan untuk update single column field
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.
func (db *DB) Updates(values interface{}) (tx *DB)Berikut adalah contoh
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.
func Expr(expr string, args ...interface{}) clause.ExprLihat contoh berikut
// 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.
func (db *DB) Delete(value interface{}, conds ...interface{}) (tx *DB)Misalnya langsung mengirim struktur
var p Person
db.First(&p)
// DELETE FROM `person` WHERE `person`.`id` = 2
db.Delete(&p)Atau
var p Person
db.First(&p)
// DELETE FROM `person` WHERE `person`.`id` = 2
db.Model(p).Delete(nil)Atau menentukan kondisi
// DELETE FROM `person` WHERE id = 2
db.Model(Person{}).Where("id = ?", p.Id).Delete(nil)Juga dapat disederhanakan menjadi
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
// 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
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
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.
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.
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
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
func (db *DB) Association(column string) *AssociationMenerima parameter relasi nilainya harus nama field dari tipe yang direferensikan yang di-embed dalam struktur referensi.
db.Model(&person).Association("Mom").Find(&mom)Misalnya relasi find ibu seseorang parameter Association adalah Mom yaitu nama field Person.Mom.
Create Relasi
// 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.
// 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
// 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
// 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
db.Model(&jack).Association("Houses").Clear()Jika ingin menghapus record entitas yang sesuai dapat menambahkan operasi Unscoped setelah operasi Association (tidak akan mempengaruhi many2many)
db.Model(&jack).Association("Houses").Unscoped().Delete(&houses)Untuk one-to-many dan many-to-many dapat menggunakan operasi Select untuk delete record
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
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
[{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.
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.
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
momDapat 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.
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.
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
// 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() *DBLihat contoh setelah memulai transaksi harus menggunakan tx untuk operasi orm.
var ps []Person
tx := db.Begin()
err := tx.Create(&ps).Error
if err != nil {
tx.Rollback()
return
}
err = tx.Create(&ps).Error
if err != nil {
tx.Rollback()
return
}
err = tx.Model(Person{}).Where("id = ?", 1).Update("name", "jack").Error
if err != nil {
tx.Rollback()
return
}
tx.Commit()Dapat menentukan savepoint
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前往官方文档了解.
