Gorm Thư viện ORM Database
Tài liệu chính thức: GORM - The fantastic ORM library for Golang, aims to be developer friendly.
Kho lưu trữ: go-gorm/gorm: The fantastic ORM library for Golang, aims to be developer friendly (github.com)
Trong cộng đồng Go, đối với phần tương tác database, có hai phe phái, một phe thích các thư viện đơn giản như sqlx, chức năng không mạnh mẽ nhưng có thể kiểm soát SQL mọi lúc, tối ưu hiệu suất đến mức tối đa. Phe còn lại thích ORM để phát triển hiệu quả, có thể tiết kiệm nhiều phiền phức không cần thiết trong quá trình phát triển. Khi nhắc đến ORM, trong cộng đồng Go không thể không nhắc đến gorm, đây là một ORM lâu đời, tương tự như xorm, ent và các ORM trẻ hơn khác. Bài viết này sẽ nói về nội dung liên quan đến gorm, bài viết chỉ giải thích các nội dung cơ bản, coi như là mở đầu, muốn tìm hiểu chi tiết sâu hơn có thể đọc tài liệu chính thức, tài liệu tiếng Trung của nó đã khá hoàn chỉnh, và tác giả cũng là một trong những người dịch tài liệu gorm.
Đặc điểm
- ORM đầy đủ chức năng
- Association (Has One, Has Many, Belongs To, Many To Many, Polymorphism, Single-table inheritance)
- Hooks (Before/After Create/Save/Update/Delete/Find)
- Preload với hỗ trợ Preload, Joins
- Transaction, Nested Transaction, Save Point, Rollback To Saved Point
- Context, Prepared Statement mode, DryRun mode
- Batch Insert, FindInBatches, Find/Create with Map, CRUD với SQL expression, Context Valuer
- SQL Builder, Upsert, Lock, Optimizer/Index/Comment Hint, Named Parameters, Subquery
- Composite Primary Key, Index, Constraint
- Auto Migration
- Custom Logger
- Flexible plugin API: Database Resolver (multiple databases, read/write splitting), Prometheus...
- Mọi tính năng đều được kiểm thử kỹ lưỡng
- Developer friendly
Gorm cũng có một số nhược điểm, ví dụ như hầu hết các tham số method đều là kiểu interface, nếu không đọc tài liệu thì khó mà biết được nên truyền gì, đôi khi có thể truyền struct, đôi khi có thể truyền string, đôi khi có thể truyền map, đôi khi có thể truyền slice, ngữ nghĩa khá mơ hồ, và nhiều trường hợp vẫn cần tự viết SQL.
Có hai ORM thay thế có thể thử, đầu tiên là aorm, mới开源不久, nó không cần tự viết tên trường của bảng, hầu hết là chain operation, dựa trên reflection, vì số sao không nhiều nên có thể chờ thêm. Thứ hai là ent, ORM do facebook开源, nó cũng hỗ trợ chain operation, và hầu hết không cần tự viết SQL,设计理念 dựa trên graph (graph trong cấu trúc dữ liệu), implementation dựa trên code generation thay vì reflection (khá đồng ý với điều này), nhưng tài liệu là tiếng Anh, có ngưỡng tiếp cận nhất định.
Cài đặt
Cài đặt thư viện gorm
$ go get -u gorm.io/gormKết nối
Hiện tại gorm hỗ trợ các database sau
- 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 tương thích giao thức mysql - ClickHouse:
"gorm.io/driver/clickhouse"
Ngoài ra còn có một số driver database khác do các nhà phát triển bên thứ ba cung cấp, ví dụ như driver oracle CengSin/oracle. Bài viết này sẽ sử dụng MySQL để demo, sử dụng database nào thì cần cài đặt driver tương ứng, ở đây cài đặt driver Mysql của gorm.
$ go get -u gorm.io/driver/mysqlSau đó kết nối với database bằng dsn (data source name), driver library sẽ tự động parse dsn thành cấu hình tương ứng
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")
}Hoặc truyền cấu hình thủ công
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")
}Hai cách đều tương đương, tùy theo thói quen sử dụng.
Cấu hình kết nối
Bằng cách truyền cấu trúc gorm.Config, chúng ta có thể kiểm soát một số hành vi của gorm
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})Dưới đây là giải thích đơn giản, có thể cấu hình theo nhu cầu
type Config struct {
// Disable default transaction, gorm sẽ开启 transaction khi create và update đơn để保持数据一致性
SkipDefaultTransaction bool
// Naming strategy tùy chỉnh
NamingStrategy schema.Namer
// Lưu association đầy đủ
FullSaveAssociations bool
// Logger tùy chỉnh
Logger logger.Interface
// NowFunc tùy chỉnh, dùng để inject trường CreatedAt và UpdatedAt
NowFunc func() time.Time
// Chỉ生成 SQL không thực thi
DryRun bool
// Sử dụng prepared statement
PrepareStmt bool
// Sau khi kết nối, ping database
DisableAutomaticPing bool
// Bỏ qua foreign key khi migrate database
DisableForeignKeyConstraintWhenMigrating bool
// Bỏ qua relationship reference khi migrate database
IgnoreRelationshipsWhenMigrating bool
// Disable nested transaction
DisableNestedTransaction bool
// Cho phép global update, tức là update không có where
AllowGlobalUpdate bool
// Query tất cả các trường của bảng
QueryFields bool
// Batch size cho batch insert
CreateBatchSize int
// Bật chuyển đổi lỗi
TranslateError bool
// ClauseBuilders clause builder
ClauseBuilders map[string]clause.ClauseBuilder
// ConnPool db conn pool
ConnPool ConnPool
// Dialector database dialector
Dialector
// Plugins registered plugins
Plugins map[string]Plugin
callbacks *callbacks
cacheStore *sync.Map
}Model
Trong gorm, model tương ứng với bảng database, thường được thể hiện bằng struct, ví dụ như struct sau
type Person struct {
Id uint
Name string
Address string
Mom string
Dad string
}Struct内部 có thể包含 các kiểu dữ liệu cơ bản và các kiểu implements interface sql.Scanner và sql.Valuer. Mặc định, bảng được map từ struct Person là persons, theo phong cách snake case số nhiều. Tên cột cũng theo phong cách snake case, ví dụ Id tương ứng với cột id, gorm cũng cung cấp một số cách để cấu hình.
Chỉ định tên cột
Thông qua struct tag, chúng ta có thể chỉ định tên cột cho các trường struct, khi đó gorm sẽ sử dụng tên cột được chỉ định.
type Person struct {
Id uint `gorm:"column:ID;"`
Name string `gorm:"column:Name;"`
Address string
Mom string
Dad string
}Chỉ định tên bảng
Bằng cách implements interface Table, có thể chỉ định tên bảng, nó chỉ có một method là trả về tên bảng.
type Tabler interface {
TableName() string
}Trong method implements, nó trả về string person, khi migrate database, gorm sẽ tạo bảng tên là 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"
}Đối với naming strategy, cũng có thể truyền implements tùy chỉnh khi tạo kết nối để đạt được hiệu quả tùy chỉnh.
Time tracking
type Person struct {
Id uint
Name string
Address string
Mom string
Dad string
CreatedAt sql.NullTime
UpdatedAt sql.NullTime
}
func (p Person) TableName() string {
return "person"
}Khi包含 các trường CreatedAt hoặc UpdatedAt, khi tạo hoặc cập nhật record, nếu giá trị bằng 0, gorm sẽ tự động sử dụng time.Now() để đặt thời gian.
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 cũng hỗ trợ tracking 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;"`
}Khi thực thi Create, tương đương với SQL sau
INSERT INTO `person` (`name`,`address`,`mom`,`dad`,`created_at`,`updated_at`) VALUES ('jack','usa','lili','tom',1698216540519000000,1698216540)Trong thực tế, nếu cần tracking thời gian, khuyến nghị lưu timestamp ở backend, xử lý đơn giản hơn trong trường hợp cross timezone.
Model
Gorm cung cấp struct Model preset, nó包含 ID primary key, và hai trường time tracking, và một trường soft delete.
type Model struct {
ID uint `gorm:"primarykey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt DeletedAt `gorm:"index"`
}Khi sử dụng chỉ cần embed vào model entity của bạn
type Order struct {
gorm.Model
Name string
}Như vậy nó sẽ tự động có tất cả các đặc tính của gorm.Model.
Primary Key
Mặc định, trường tên id là primary key, có thể chỉ định trường primary key bằng struct tag
type Person struct {
Id uint `gorm:"primaryKey;"`
Name string
Address string
Mom string
Dad string
CreatedAt sql.NullTime
UpdatedAt sql.NullTime
}Nhiều trường tạo thành 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
Có thể chỉ định column index thông qua struct tag index
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;"`
}Trong struct trên, đã tạo unique index cho trường Address. Hai trường sử dụng cùng tên index sẽ tạo 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
Trong struct, định nghĩa relationship foreign key được thực hiện bằng cách embed struct, ví dụ
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;"`
}Trong ví dụ, struct Person có hai foreign key, lần lượt reference primary key của hai struct Dad và Mom, mặc định là reference primary key. Person đối với Dad và Mom là relationship one-to-one, một người chỉ có thể có một cha và mẹ. Dad và Mom đối với Person là relationship one-to-many, vì cha và mẹ có thể có nhiều con.
Mom Mom `gorm:"foreignKey:MomId;"`Tác dụng của embed struct là để chỉ định foreign key và reference dễ dàng, mặc định tên trường foreign key theo format TênKiểuReference+Id, ví dụ MomId. Mặc định là reference primary key, có thể chỉ định reference trường nào đó thông qua struct tag
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;"`
}Trong đó constraint:OnUpdate:CASCADE,OnDelete:SET NULL; là định nghĩa foreign key constraint.
Hooks
Một model entity có thể自定义 hooks
- Create
- Update
- Delete
- Query
Các interface tương ứng như sau
// Trigger trước khi tạo
type BeforeCreateInterface interface {
BeforeCreate(*gorm.DB) error
}
// Trigger sau khi tạo
type AfterCreateInterface interface {
AfterCreate(*gorm.DB) error
}
// Trigger trước khi cập nhật
type BeforeUpdateInterface interface {
BeforeUpdate(*gorm.DB) error
}
// Trigger sau khi cập nhật
type AfterUpdateInterface interface {
AfterUpdate(*gorm.DB) error
}
// Trigger trước khi lưu
type BeforeSaveInterface interface {
BeforeSave(*gorm.DB) error
}
// Trigger sau khi lưu
type AfterSaveInterface interface {
AfterSave(*gorm.DB) error
}
// Trigger trước khi xóa
type BeforeDeleteInterface interface {
BeforeDelete(*gorm.DB) error
}
// Trigger sau khi xóa
type AfterDeleteInterface interface {
AfterDelete(*gorm.DB) error
}
// Trigger sau khi query
type AfterFindInterface interface {
AfterFind(*gorm.DB) error
}Struct có thể自定义 một số hành vi bằng cách implements các interface này.
Tags
Dưới đây là một số tags mà gorm hỗ trợ
| Tag | Mô tả |
|---|---|
column | Chỉ định tên cột db |
type | Kiểu dữ liệu cột, khuyến nghị sử dụng kiểu通用 tương thích tốt, ví dụ: bool, int, uint, float, string, time, bytes đều được hỗ trợ bởi tất cả database và có thể sử dụng cùng với các tags khác, ví dụ: not null, size, autoIncrement... Cũng hỗ trợ chỉ định kiểu dữ liệu database như varbinary(8). Khi sử dụng kiểu dữ liệu database chỉ định, cần là kiểu dữ liệu database đầy đủ, như: MEDIUMINT UNSIGNED not NULL AUTO_INCREMENT |
serializer | Chỉ định serializer để serialize hoặc deserialize dữ liệu vào database, ví dụ: serializer:json/gob/unixtime |
size | Định nghĩa kích thước hoặc độ dài của kiểu dữ liệu cột, ví dụ size: 256 |
primaryKey | Định nghĩa cột là primary key |
unique | Định nghĩa cột là unique key |
default | Định nghĩa giá trị mặc định của cột |
precision | Chỉ định precision của cột |
scale | Chỉ định scale của cột |
not null | Chỉ định cột là NOT NULL |
autoIncrement | Chỉ định cột là auto increment |
autoIncrementIncrement | Auto increment increment, kiểm soát khoảng cách giữa các record liên tiếp |
embedded | Embedded field |
embeddedPrefix | Prefix tên cột cho embedded field |
autoCreateTime | Track thời gian hiện tại khi tạo, đối với trường int, nó sẽ track timestamp giây, có thể sử dụng nano/milli để track timestamp nanosecond, millisecond, ví dụ: autoCreateTime:nano |
autoUpdateTime | Track thời gian hiện tại khi tạo/cập nhật, đối với trường int, nó sẽ track timestamp giây, có thể sử dụng nano/milli để track timestamp nanosecond, millisecond, ví dụ: autoUpdateTime:milli |
index | Tạo index theo tham số, nhiều trường sử dụng cùng tên sẽ tạo composite index, xem Indexes để biết chi tiết |
uniqueIndex | Giống index nhưng tạo unique index |
check | Tạo check constraint, ví dụ check:age > 13, xem Constraints để biết chi tiết |
<- | Đặt quyền ghi của trường, <-:create chỉ tạo, <-:update chỉ cập nhật, <-:false không có quyền ghi, <- quyền tạo và cập nhật |
-> | Đặt quyền đọc của trường, ->:false không có quyền đọc |
- | Bỏ qua trường, -表示 không đọc ghi, -:migration表示 không có quyền migration, -:all表示 không có quyền đọc ghi migration |
comment | Thêm comment cho trường khi migration |
foreignKey | Chỉ định cột của model hiện tại làm foreign key của join table |
references | Chỉ định tên cột của reference table, sẽ được map thành foreign key của join table |
polymorphic | Chỉ định polymorphic type, ví dụ tên model |
polymorphicValue | Chỉ định polymorphic value, mặc định là tên bảng |
many2many | Chỉ định tên join table |
joinForeignKey | Chỉ định tên foreign key column của join table, sẽ được map到 current table |
joinReferences | Chỉ định tên foreign key column của join table, sẽ được map到 reference table |
constraint | Relationship constraints, ví dụ: OnUpdate, OnDelete |
Migration
Method AutoMigrate sẽ giúp chúng ta tự động migrate, nó sẽ tạo bảng, constraints, indexes, foreign keys, v.v.
func (db *DB) AutoMigrate(dst ...interface{}) errorVí dụ
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`))Hoặc có thể thao tác thủ công, truy cập interface Migrator thông qua method Migrator
func (db *DB) Migrator() MigratorNó hỗ trợ các method interface sau
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)
}Danh sách method liên quan đến nhiều khía cạnh database, table, column, view, index, constraint, cho phép người dùng cần tùy chỉnh có thể thao tác chi tiết hơn.
Chỉ định table comment
Khi migrate, nếu muốn thêm table comment, có thể thiết lập như sau
db.Set("gorm:table_options", " comment 'person table'").Migrator().CreateTable(Person{})Cần lưu ý nếu sử dụng method AutoMigrate() để migrate, và các struct có relationship reference, gorm sẽ recursive tạo bảng reference trước, điều này dẫn đến table comment của bảng được reference và bảng reference đều bị lặp, vì vậy khuyến nghị sử dụng method CreateTable để tạo.
TIP
Khi tạo bảng, method CreateTable cần đảm bảo bảng được reference được tạo trước bảng reference, nếu không sẽ báo lỗi, trong khi method AutoMigrate thì không cần, vì nó sẽ recursive tạo theo relationship reference.
Create
Create
Khi tạo record mới, hầu hết sẽ sử dụng method Create
func (db *DB) Create(value interface{}) (tx *DB)Hiện có struct sau
type Person struct {
Id uint `gorm:"primaryKey;"`
Name string
}Tạo một record
user := Person{
Name: "jack",
}
// Phải truyền reference
db = db.Create(&user)
// Lỗi xảy ra trong quá trình thực thi
err = db.Error
// Số lượng tạo
affected := db.RowsAffectedSau khi tạo xong, gorm sẽ ghi primary key vào struct user, vì vậy phải truyền pointer. Nếu truyền một slice, sẽ tạo batch
user := []Person{
{Name: "jack"},
{Name: "mike"},
{Name: "lili"},
}
db = db.Create(&user)Tương tự, gorm cũng sẽ ghi primary key vào slice. Khi dữ liệu quá lớn, cũng có thể sử dụng method CreateInBatches để tạo theo batch, vì SQL生成 INSERT INTO table VALUES (),() sẽ rất dài, mỗi database có giới hạn độ dài SQL, vì vậy cần thiết có thể chọn tạo theo batch.
db = db.CreateInBatches(&user, 50)Ngoài ra, method Save cũng có thể tạo record, tác dụng của nó là khi primary key khớp thì cập nhật record, ngược lại thì insert.
func (db *DB) Save(value interface{}) (tx *DB)user := []Person{
{Name: "jack"},
{Name: "mike"},
{Name: "lili"},
}
db = db.Save(&user)Upsert
Method Save chỉ có thể khớp primary key, chúng ta có thể xây dựng Clause để hoàn thành upsert tùy chỉnh hơn. Ví dụ dòng code sau
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "name"}},
DoNothing: false,
DoUpdates: clause.AssignmentColumns([]string{"address"}),
UpdateAll: false,
}).Create(&p)Tác dụng của nó là khi trường name conflict, cập nhật giá trị của trường address, không conflict thì tạo record mới. Cũng có thể không làm gì khi conflict
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "name"}},
DoNothing: true,
}).Create(&p)Hoặc trực tiếp cập nhật tất cả các trường
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "name"}},
UpdateAll: true,
}).Create(&p)Trước khi sử dụng upsert, nhớ thêm index cho các trường conflict.
Query
First
Gorm cung cấp khá nhiều method cho query, đầu tiên là method First
func (db *DB) First(dest interface{}, conds ...interface{}) (tx *DB)Tác dụng của nó là tìm record đầu tiên theo primary key ascending, ví dụ
var person Person
result := db.First(&person)
err := result.Error
affected := result.RowsAffectedTruyền pointer dest để gorm map dữ liệu query được vào struct.
Hoặc sử dụng method Table và Model để chỉ định bảng query, cái trước nhận string table name, cái sau nhận entity model.
db.Table("person").Find(&p)
db.Model(Person{}).Find(&p)TIP
Nếu pointer truyền vào包含 entity model ví dụ như struct pointer, hoặc struct slice pointer, thì không cần chỉ định bảng nào để query, quy tắc này áp dụng cho tất cả các thao tác增删改查.
Take
Method Take tương tự First, khác biệt là không sort theo 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 dùng để query batch một column của bảng, kết quả query có thể collect vào slice của kiểu chỉ định, không nhất thiết phải là slice của entity type.
func (db *DB) Pluck(column string, dest interface{}) (tx *DB)Ví dụ collect tất cả address của mọi người vào string slice
var adds []string
// SELECT `address` FROM `person` WHERE name IN ('jack','lili')
db.Model(Person{}).Where("name IN ?", []string{"jack", "lili"}).Pluck("address", &adds)Thực tế tương đương với
db.Select("address").Where("name IN ?", []string{"jack", "lili"}).Find(&adds)Count
Method Count dùng để thống kê số lượng entity record
func (db *DB) Count(count *int64) (tx *DB)Xem ví dụ sử dụng
var count int64
// SELECT count(*) FROM `person`
db.Model(Person{}).Count(&count)Find
Method thường dùng nhất cho query batch là Find
func (db *DB) Find(dest interface{}, conds ...interface{}) (tx *DB)Nó sẽ tìm tất cả các record phù hợp theo điều kiện đã cho
// SELECT * FROM `person`
var ps []Person
db.Find(&ps)Select
Gorm mặc định query tất cả các trường, có thể chỉ định trường thông qua method Select
func (db *DB) Select(query interface{}, args ...interface{}) (tx *DB)Ví dụ
// SELECT `address`,`name` FROM `person` ORDER BY `person`.`id` LIMIT 1
db.Select("address", "name").First(&p)Tương đương với
db.Select([]string{"address", "name"}).First(&p)Đồng thời, có thể sử dụng method Omit để bỏ qua các trường
func (db *DB) Omit(columns ...string) (tx *DB)Ví dụ
// 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)Các trường được chọn hoặc bỏ qua bởi Select và Omit sẽ có tác dụng khi tạo query cập nhật.
Where
Query điều kiện sẽ sử dụng method Where
func (db *DB) Where(query interface{}, args ...interface{}) (tx *DB)Dưới đây là ví dụ đơn giản
var p Person
db.Where("id = ?", 1).First(&p)Sử dụng nhiều Where trong chain operation sẽ xây dựng nhiều statement AND, ví dụ
// SELECT * FROM `person` WHERE id = 1 AND name = 'jack' ORDER BY `person`.`id` LIMIT 1
db.Where("id = ?", 1).Where("name = ?", "jack").First(&p)Hoặc sử dụng method Or để xây dựng 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)Và method Not, đều tương tự
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)Đối với điều kiện IN, có thể truyền slice trực tiếp trong method Where.
db.Where("address IN ?", []string{"cn", "us"}).Find(&ps)Hoặc điều kiện IN nhiều column, cần sử dụng kiểu [][]any để chứa tham số
// 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 hỗ trợ sử dụng nhóm where, kết hợp các statement trên
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
Sort sẽ sử dụng method Order
func (db *DB) Order(value interface{}) (tx *DB)Xem ví dụ sử dụng
var ps []Person
// SELECT * FROM `person` ORDER BY name ASC, id DESC
db.Order("name ASC, id DESC").Find(&ps)Cũng có thể gọi nhiều lần
// SELECT * FROM `person` ORDER BY name ASC, id DESC,address
db.Order("name ASC, id DESC").Order("address").Find(&ps)Limit
Method Limit và Offset thường dùng cho query phân trang
func (db *DB) Limit(limit int) (tx *DB)
func (db *DB) Offset(offset int) (tx *DB)Dưới đây là ví dụ phân trang đơn giản
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 và Having thường dùng cho operation group
func (db *DB) Group(name string) (tx *DB)
func (db *DB) Having(query interface{}, args ...interface{}) (tx *DB)Xem ví dụ
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 thường dùng cho deduplication
func (db *DB) Distinct(args ...interface{}) (tx *DB)Xem ví dụ
// SELECT DISTINCT `name` FROM `person` WHERE address IN ('cn','us')
db.Where("address IN ?", []string{"cn", "us"}).Distinct("name").Find(&ps)Subquery
Subquery là nested query, ví dụ muốn query tất cả người có id lớn hơn giá trị trung bình
// 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 sử dụng clause clause.Locking để cung cấp hỗ trợ 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)Iteration
Có thể lấy iterator thông qua method Rows
func (db *DB) Rows() (*sql.Rows, error)Bằng cách duyệt iterator, sử dụng method ScanRows có thể scan kết quả của mỗi row vào struct.
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
Đã đề cập method Save khi create, nó cũng có thể dùng để update record, và nó sẽ update tất cả các trường, ngay cả khi một số trường struct là zero value, nhưng nếu primary key không khớp thì sẽ thực hiện insert.
var p Person
db.First(&p)
p.Address = "poland"
// UPDATE `person` SET `name`='json',`address`='poland' WHERE `id` = 2
db.Save(&p)Có thể thấy nó đã thêm tất cả các trường trừ primary key vào statement SET.
Update
Vì vậy trong hầu hết trường hợp, khuyến nghị sử dụng method Update
func (db *DB) Update(column string, value interface{}) (tx *DB)Nó chủ yếu dùng để 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 dùng để update multi-column, nhận struct và map làm tham số, và khi struct field là zero value, sẽ bỏ qua trường đó, nhưng trong map thì không.
func (db *DB) Updates(values interface{}) (tx *DB)Dưới đây là ví dụ
var p Person
db.First(&p)
// UPDATE `person` SET `name`='jojo',`address`='poland' WHERE `id` = 2
db.Model(p).Updates(Person{Name: "jojo", Address: "poland"})
// UPDATE `person` SET `address`='poland',`name`='jojo' WHERE `id` = 2
db.Model(p).Updates(map[string]any{"name": "jojo", "address": "poland"})SQL Expression
Đôi khi, thường cần thực hiện một số operation như increment hoặc decrement với chính field, thông thường là query trước rồi tính toán sau đó update, hoặc sử dụng SQL expression.
func Expr(expr string, args ...interface{}) clause.ExprXem ví dụ sau
// 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
Trong gorm, delete record sẽ sử dụng method Delete, có thể truyền entity struct, cũng có thể truyền condition.
func (db *DB) Delete(value interface{}, conds ...interface{}) (tx *DB)Ví dụ truyền struct
var p Person
db.First(&p)
// DELETE FROM `person` WHERE `person`.`id` = 2
db.Delete(&p)Hoặc
var p Person
db.First(&p)
// DELETE FROM `person` WHERE `person`.`id` = 2
db.Model(p).Delete(nil)Hoặc chỉ định condition
// DELETE FROM `person` WHERE id = 2
db.Model(Person{}).Where("id = ?", p.Id).Delete(nil)Cũng có thể viết gọn
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)Delete batch thì truyền 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
Nếu entity model của bạn sử dụng soft delete, khi delete, mặc định thực hiện update operation, nếu muốn delete vĩnh viễn có thể sử dụng method Unscoped
db.Unscoped().Delete(&Person{}, []uint{1, 2, 3})Association Definition
Gorm cung cấp khả năng tương tác association table, định nghĩa association giữa các struct thông qua embed struct và field.
One-to-One
Relationship one-to-one là đơn giản nhất, bình thường một người chỉ có thể có một mẹ, xem struct sau
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
}Struct Person thông qua embed struct Mom, thực hiện reference kiểu Mom, trong đó Person.MomId là field reference, primary key Mom.Id là field được reference, như vậy hoàn thành association one-to-one. Cách tùy chỉnh foreign key và reference và constraint và quy tắc foreign key mặc định đã được đề cập trong Foreign Key Definition, không nhắc lại
TIP
Đối với foreign key field, khuyến nghị sử dụng kiểu do package sql cung cấp, vì foreign key mặc định có thể là NULL, khi sử dụng Create để tạo record, nếu sử dụng kiểu thông thường, zero value 0 cũng sẽ được tạo, foreign key không tồn tại được tạo rõ ràng không được phép.
One-to-Many
Thêm một struct school, relationship giữa school và student là one-to-many, một school có nhiều student, nhưng một student chỉ có thể học ở một 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 là kiểu []Person, biểu thị có thể sở hữu nhiều student, và Person thì phải包含 foreign key reference School, tức là Person.SchoolId.
Many-to-Many
Một người có thể sở hữu nhiều house, một house cũng có thể chứa nhiều người, đây là relationship 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 và House互相 giữ slice của nhau biểu thị relationship many-to-many, relationship many-to-many thường cần tạo join table, chỉ định join table thông qua many2many, foreign key của join table phải được chỉ định chính xác.
Sau khi tạo struct xong để gorm tự động migrate vào database
tables := []any{
School{},
Mom{},
Person{},
House{},
PersonHouse{},
}
for _, table := range tables {
db.Migrator().CreateTable(&table)
}Chú ý thứ tự tạo giữa bảng reference và bảng được reference.
Association Operation
Sau khi tạo xong ba relationship association trên, tiếp theo là cách sử dụng association để增删改查. Chủ yếu sẽ sử dụng method Association
func (db *DB) Association(column string) *AssociationNó nhận một tham số association, giá trị của nó nên là tên field của kiểu được reference trong struct embed reference.
db.Model(&person).Association("Mom").Find(&mom)Ví dụ association query mẹ của một người, tham số Association là Mom, tức là tên field Person.Mom.
Create Association
// Định nghĩa dữ liệu
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)
// Thêm association Person với Mom, association 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)
// Thêm association school với Person, association 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})
// Thêm association Person với Houses, association 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})Nếu tất cả các record không tồn tại, khi thực hiện create association, cũng sẽ tạo record trước rồi tạo association.
Find Association
Dưới đây demo cách find association.
// Find association 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 association 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 association 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 association sẽ tìm record phù hợp trong reference table dựa trên dữ liệu hiện có, đối với relationship many-to-many, gorm sẽ tự động hoàn thành quá trình join table.
Update Association
Dưới đây demo cách update association
// Update association 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 association 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 association 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"}})Khi update association, nếu dữ liệu được reference và reference đều không tồn tại, gorm sẽ cố gắng tạo chúng.
Delete Association
Dưới đây demo cách delete association
// Delete association 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 association 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 association 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 association chỉ xóa relationship reference giữa chúng, không xóa entity record. Cũng có thể sử dụng method Clear để xóa trực tiếp association
db.Model(&jack).Association("Houses").Clear()Nếu muốn xóa entity record tương ứng, có thể thêm operation Unscoped sau operation Association (không ảnh hưởng many2many)
db.Model(&jack).Association("Houses").Unscoped().Delete(&houses)Đối với one-to-many và many-to-many, có thể sử dụng operation Select để xóa record
var (
mit School
)
db.Where("name = ?", "mit").First(&mit)
db.Select("Persons").Delete(&mit)Preload
Preload dùng để query association data, đối với entity có relationship association, nó sẽ preload entity được association reference trước. Query association đã đề cập trước đó là query cho relationship association, preload là query trực tiếp cho entity record, bao gồm tất cả relationship association. Về mặt syntax, query association cần query []Person chỉ định trước, sau đó dựa vào []Person để query []Mom association, preload từ syntax trực tiếp query []Person, và cũng sẽ load tất cả relationship association, nhưng thực tế SQL thực thi của chúng đều gần giống nhau. Xem ví dụ
var users []Person
// SELECT * FROM `moms` WHERE `moms`.`id` = 1
// SELECT * FROM `people`
db.Preload("Mom").Find(&users)Đây là ví dụ query association one-to-one, output của nó
[{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:[]}]Có thể thấy đã query ra Mom association, nhưng không preload relationship school, tất cả struct School đều là zero value. Cũng có thể sử dụng clause.Associations để biểu thị preload tất cả relationship, trừ nested relationship.
db.Preload(clause.Associations).Find(&users)Xem ví dụ nested preload, tác dụng của nó là query tất cả student của tất cả school association và mẹ mà mỗi student association và house mà mỗi student sở hữu, và còn query tập hợp chủ nhân của mỗi house, school->student->house->student.
var schools []School
db.Preload("Persons").
Preload("Persons.Mom").
Preload("Persons.Houses").
Preload("Persons.Houses.Persons").Find(&schools)
// Output code, logic có thể bỏ qua
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 là
school MIT
person jack
mom jenny
house h1 owner [jack]
house h2 owner [jack]
person mike
momCó thể thấy output mẹ của mỗi student của mỗi school và house của chúng, và tất cả chủ nhân của house.
Transaction
Gorm mặc định开启 transaction, bất kỳ insert và update operation nào thất bại đều sẽ rollback, có thể tắt trong Connection Configuration, hiệu suất大概 tăng khoảng 30%. Việc sử dụng transaction trong gorm có nhiều cách, dưới đây giới thiệu đơn giản.
Automatic
Closure transaction, thông qua method Transaction, truyền một closure function, nếu giá trị trả về của function không nil, thì sẽ tự động rollback.
func (db *DB) Transaction(fc func(tx *DB) error, opts ...*sql.TxOptions) (err error)Xem ví dụ, operation trong closure nên sử dụng tham số tx, thay vì db bên ngoài.
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
Khuyến nghị sử dụng manual transaction, do chúng ta tự kiểm soát khi nào rollback, khi nào commit. Manual transaction sẽ sử dụng ba method sau
// Method Begin dùng để开启 transaction
func (db *DB) Begin(opts ...*sql.TxOptions) *DB
// Method Rollback dùng để rollback transaction
func (db *DB) Rollback() *DB
// Method Commit dùng để commit transaction
func (db *DB) Commit() *DBXem ví dụ, sau khi开启 transaction, nên sử dụng tx để操作 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()Có thể chỉ định 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()Tổng kết
Nếu bạn đã đọc hết tất cả nội dung trên và đã gõ code, thì bạn có thể sử dụng gorm để thực hiện增删改查 database, ngoài những operation này, gorm còn có nhiều chức năng khác, chi tiết hơn có thể tìm hiểu tại tài liệu chính thức.
