GORM 2.0 發行說明

GORM 2.0 是從頭開始重寫,它引進了一些不相容的 API 變更和許多改進

重點

  • 效能改善
  • 模組化
  • 支援背景、批次插入、預先準備的陳述模式、DryRun 模式、聯結預載入、尋找對應、從對應建立、批次尋找
  • 支援巢狀交易/儲存點/回滾至儲存點
  • 支援 SQL 建構器、命名參數、群組條件、Upsert、鎖定、最佳化器/索引/註解提示、子查詢改進、使用 SQL Expr 和背景 Valuer 進行 CRUD
  • 支援完整的自參考關係、聯結表格改進、批次資料的關聯模式
  • 允許多個欄位追蹤建立/更新時間,支援 UNIX(毫秒/奈秒)秒數
  • 支援欄位權限:唯讀、唯寫、唯建立、唯更新、忽略
  • 新的外掛系統,提供適用於多個資料庫、讀/寫分流、prometheus 整合的官方外掛…
  • 新的 Hooks API:與外掛統一介面
  • 新的 Migrator:允許為關係建立資料庫外來鍵,更智慧的 AutoMigrate、約束/檢查器支援、增強索引支援
  • 新的 Logger:背景支援、改進的可擴充性
  • 統一的命名策略:表格名稱、欄位名稱、聯結表格名稱、外來鍵、檢查器、索引名稱規則
  • 更好的自訂資料類型支援(例如:JSON)

如何升級

  • GORM 的開發已移至 github.com/go-gorm,其匯入路徑已變更為 gorm.io/gorm,對於以前的專案,您可以繼續使用 github.com/jinzhu/gorm GORM V1 文件
  • 資料庫驅動程式已拆分為獨立的專案,例如:github.com/go-gorm/sqlite,其匯入路徑也已變更為 gorm.io/driver/sqlite

安裝

go get gorm.io/gorm
// **NOTE** GORM `v2.0.0` released with git tag `v1.20.0`

快速入門

import (
"gorm.io/gorm"
"gorm.io/driver/sqlite"
)

func init() {
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{})

// Most CRUD API kept compatibility
db.AutoMigrate(&Product{})
db.Create(&user)
db.First(&user, 1)
db.Model(&user).Update("Age", 18)
db.Model(&user).Omit("Role").Updates(map[string]interface{}{"Name": "jinzhu", "Role": "admin"})
db.Delete(&user)
}

主要功能

此版本說明僅涵蓋 GORM V2 中引入的主要變更,作為快速參考清單

Context 支援

  • 資料庫操作支援使用 WithContext 方法的 context.Context
  • 記錄器也接受 context 以進行追蹤
db.WithContext(ctx).Find(&users)

批次插入

若要有效率地插入大量記錄,請將片段傳遞給 Create 方法。GORM 會產生單一 SQL 陳述式來插入所有資料並填入主鍵值,也會呼叫掛鉤方法。

var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
db.Create(&users)

for _, user := range users {
user.ID // 1,2,3
}

您可以在使用 CreateInBatches 進行建立時指定批次大小,例如

var users = []User{{Name: "jinzhu_1"}, ...., {Name: "jinzhu_10000"}}

// batch size 100
db.CreateInBatches(users, 100)

準備陳述式模式

準備陳述式模式會建立準備好的陳述式並將其快取,以加速後續呼叫

// globally mode, all operations will create prepared stmt and cache to speed up
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{PrepareStmt: true})

// session mode, create prepares stmt and speed up current session operations
tx := db.Session(&Session{PrepareStmt: true})
tx.First(&user, 1)
tx.Find(&users)
tx.Model(&user).Update("Age", 18)

DryRun 模式

產生 SQL 但不執行,可用於檢查或測試產生的 SQL

stmt := db.Session(&Session{DryRun: true}).Find(&user, 1).Statement
stmt.SQL.String() //=> SELECT * FROM `users` WHERE `id` = $1 // PostgreSQL
stmt.SQL.String() //=> SELECT * FROM `users` WHERE `id` = ? // MySQL
stmt.Vars //=> []interface{}{1}

Join 預載入

使用 INNER JOIN 預載入關聯,並會處理 null 資料以避免掃描失敗

db.Joins("Company").Joins("Manager").Joins("Account").Find(&users, "users.id IN ?", []int{1,2})

Find To Map

將結果掃描至 map[string]interface{}[]map[string]interface{}

var result map[string]interface{}
db.Model(&User{}).First(&result, "id = ?", 1)

Create From Map

map[string]interface{}[]map[string]interface{} 建立

db.Model(&User{}).Create(map[string]interface{}{"Name": "jinzhu", "Age": 18})

datas := []map[string]interface{}{
{"Name": "jinzhu_1", "Age": 19},
{"name": "jinzhu_2", "Age": 20},
}

db.Model(&User{}).Create(datas)

FindInBatches

批次查詢和處理記錄

result := db.Where("age>?", 13).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error {
// batch processing
return nil
})

巢狀交易

db.Transaction(func(tx *gorm.DB) error {
tx.Create(&user1)

tx.Transaction(func(tx2 *gorm.DB) error {
tx.Create(&user2)
return errors.New("rollback user2") // rollback user2
})

tx.Transaction(func(tx2 *gorm.DB) error {
tx.Create(&user3)
return nil
})

return nil // commit user1 and user3
})

儲存點、RollbackTo

tx := db.Begin()
tx.Create(&user1)

tx.SavePoint("sp1")
tx.Create(&user2)
tx.RollbackTo("sp1") // rollback user2

tx.Commit() // commit user1

命名參數

GORM 支援使用 sql.NamedArgmap[string]interface{} 作為命名參數

db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)
// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu"

db.Where("name1 = @name OR name2 = @name", map[string]interface{}{"name": "jinzhu2"}).First(&result3)
// SELECT * FROM `users` WHERE name1 = "jinzhu2" OR name2 = "jinzhu2" ORDER BY `users`.`id` LIMIT 1

db.Raw(
"SELECT * FROM users WHERE name1 = @name OR name2 = @name2 OR name3 = @name",
sql.Named("name", "jinzhu1"), sql.Named("name2", "jinzhu2"),
).Find(&user)
// SELECT * FROM users WHERE name1 = "jinzhu1" OR name2 = "jinzhu2" OR name3 = "jinzhu1"

db.Exec(
"UPDATE users SET name1 = @name, name2 = @name2, name3 = @name",
map[string]interface{}{"name": "jinzhu", "name2": "jinzhu2"},
)
// UPDATE users SET name1 = "jinzhu", name2 = "jinzhu2", name3 = "jinzhu"

群組條件

db.Where(
db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")),
).Or(
db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"),
).Find(&pizzas)

// SELECT * FROM pizzas WHERE (pizza = 'pepperoni' AND (size = 'small' OR size = 'medium')) OR (pizza = 'hawaiian' AND size = 'xlarge')

子查詢

// Where SubQuery
db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders)

// From SubQuery
db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18}).Find(&User{})
// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE age = 18

// Update SubQuery
db.Model(&user).Update(
"price", db.Model(&Company{}).Select("name").Where("companies.id = users.company_id"),
)

更新插入

clause.OnConflict 為不同的資料庫(SQLite、MySQL、PostgreSQL、SQL Server)提供相容的更新插入支援

import "gorm.io/gorm/clause"

db.Clauses(clause.OnConflict{DoNothing: true}).Create(&users)

db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "id"}},
DoUpdates: clause.Assignments(map[string]interface{}{"name": "jinzhu", "age": 18}),
}).Create(&users)
// MERGE INTO "users" USING *** WHEN NOT MATCHED THEN INSERT *** WHEN MATCHED THEN UPDATE SET ***; SQL Server
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE name="jinzhu", age=18; MySQL

db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "id"}},
DoUpdates: clause.AssignmentColumns([]string{"name", "age"}),
}).Create(&users)
// MERGE INTO "users" USING *** WHEN NOT MATCHED THEN INSERT *** WHEN MATCHED THEN UPDATE SET "name"="excluded"."name"; SQL Server
// INSERT INTO "users" *** ON CONFLICT ("id") DO UPDATE SET "name"="excluded"."name", "age"="excluded"."age"; PostgreSQL
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE `name`=VALUES(name),`age=VALUES(age); MySQL

鎖定

db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)
// SELECT * FROM `users` FOR UPDATE

db.Clauses(clause.Locking{
Strength: "SHARE",
Table: clause.Table{Name: clause.CurrentTable},
}).Find(&users)
// SELECT * FROM `users` FOR SHARE OF `users`

最佳化器/索引/註解提示

import "gorm.io/hints"

// Optimizer Hints
db.Clauses(hints.New("hint")).Find(&User{})
// SELECT * /*+ hint */ FROM `users`

// Index Hints
db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})
// SELECT * FROM `users` USE INDEX (`idx_user_name`)

// Comment Hints
db.Clauses(hints.Comment("select", "master")).Find(&User{})
// SELECT /*master*/ * FROM `users`;

查看 提示 以取得詳細資料

從 SQL Expr/Context Valuer 建立 CRUD

type Location struct {
X, Y int
}

func (loc Location) GormDataType() string {
return "geometry"
}

func (loc Location) GormValue(ctx context.Context, db *gorm.DB) clause.Expr {
return clause.Expr{
SQL: "ST_PointFromText(?)",
Vars: []interface{}{fmt.Sprintf("POINT(%d %d)", loc.X, loc.Y)},
}
}

db.Create(&User{
Name: "jinzhu",
Location: Location{X: 100, Y: 100},
})
// INSERT INTO `users` (`name`,`point`) VALUES ("jinzhu",ST_PointFromText("POINT(100 100)"))

db.Model(&User{ID: 1}).Updates(User{
Name: "jinzhu",
Point: Point{X: 100, Y: 100},
})
// UPDATE `user_with_points` SET `name`="jinzhu",`point`=ST_PointFromText("POINT(100 100)") WHERE `id` = 1

查看 自訂資料類型 以取得詳細資料

欄位權限

欄位權限支援,權限等級:唯讀、唯寫、唯建立、唯更新、忽略

type User struct {
Name string `gorm:"<-:create"` // allow read and create
Name string `gorm:"<-:update"` // allow read and update
Name string `gorm:"<-"` // allow read and write (create and update)
Name string `gorm:"->:false;<-:create"` // createonly
Name string `gorm:"->"` // readonly
Name string `gorm:"-"` // ignored
}

追蹤建立/更新時間/unix (毫秒/奈秒) 秒數,適用於多個欄位

type User struct {
CreatedAt time.Time // Set to current time if it is zero on creating
UpdatedAt int // Set to current unix seconds on updaing or if it is zero on creating
Updated int64 `gorm:"autoUpdateTime:nano"` // Use unix Nano seconds as updating time
Updated2 int64 `gorm:"autoUpdateTime:milli"` // Use unix Milli seconds as updating time
Created int64 `gorm:"autoCreateTime"` // Use unix seconds as creating time
}

多個資料庫,讀取/寫入分割

GORM 提供多個資料庫,讀取/寫入分割支援,外掛程式為 DB Resolver,它也支援根據目前的結構/表格自動切換資料庫/表格,以及多個來源、複製品支援,並具有自訂負載平衡邏輯

查看 資料庫解析器 以取得詳細資料

Prometheus

GORM 提供外掛程式 Prometheus 來收集 DBStats 和使用者自訂的指標

查看 Prometheus 以取得詳細資料

命名策略

GORM 允許使用者透過覆寫預設的 NamingStrategy 來變更預設的命名慣例,它用於建立 TableNameColumnNameJoinTableNameRelationshipFKNameCheckerNameIndexName,查看 GORM 設定 以取得詳細資料

db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
NamingStrategy: schema.NamingStrategy{TablePrefix: "t_", SingularTable: true},
})

記錄器

  • 內容支援
  • 自訂/關閉記錄中的顏色
  • 慢速 SQL 記錄,預設的慢速 SQL 時間為 200 毫秒
  • 優化 SQL 日誌格式,以便可以在資料庫主控台中複製並執行

交易模式

預設情況下,所有 GORM 寫入作業都在交易中執行以確保資料一致性,如果不需要,您可以在初始化期間停用它以加快寫入作業

db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
SkipDefaultTransaction: true,
})

資料類型 (以 JSON 為例)

GORM 最佳化對自訂類型的支援,因此您可以定義一個結構來支援所有資料庫

以下以 JSON 為例 (支援 SQLite、MySQL、Postgres,請參閱:https://github.com/go-gorm/datatypes/blob/master/json.go)

import "gorm.io/datatypes"

type User struct {
gorm.Model
Name string
Attributes datatypes.JSON
}

db.Create(&User{
Name: "jinzhu",
Attributes: datatypes.JSON([]byte(`{"name": "jinzhu", "age": 18, "tags": ["tag1", "tag2"], "orgs": {"orga": "orga"}}`)),
}

// Query user having a role field in attributes
db.First(&user, datatypes.JSONQuery("attributes").HasKey("role"))
// Query user having orgs->orga field in attributes
db.First(&user, datatypes.JSONQuery("attributes").HasKey("orgs", "orga"))

智慧型選取

GORM 允許使用 Select 選取特定欄位,在 V2 中,如果您使用較小的結構進行查詢,GORM 提供智慧型選取模式

type User struct {
ID uint
Name string
Age int
Gender string
// hundreds of fields
}

type APIUser struct {
ID uint
Name string
}

// Select `id`, `name` automatically when query
db.Model(&User{}).Limit(10).Find(&APIUser{})
// SELECT `id`, `name` FROM `users` LIMIT 10

關聯批次模式

關聯模式支援批次資料,例如

// Find all roles for all users
db.Model(&users).Association("Role").Find(&roles)

// Delete User A from all user's team
db.Model(&users).Association("Team").Delete(&userA)

// Get unduplicated count of members in all user's team
db.Model(&users).Association("Team").Count()

// For `Append`, `Replace` with batch data, argument's length need to equal to data's length or will returns error
var users = []User{user1, user2, user3}
// e.g: we have 3 users, Append userA to user1's team, append userB to user2's team, append userA, userB and userC to user3's team
db.Model(&users).Association("Team").Append(&userA, &userB, &[]User{userA, userB, userC})
// Reset user1's team to userA,reset user2's team to userB, reset user3's team to userA, userB and userC
db.Model(&users).Association("Team").Replace(&userA, &userB, &[]User{userA, userB, userC})

刪除時刪除關聯

刪除記錄時,您可以使用 Select 刪除選取的單一關聯/多個關聯/多對多關聯,例如

// delete user's account when deleting user
db.Select("Account").Delete(&user)

// delete user's Orders, CreditCards relations when deleting user
db.Select("Orders", "CreditCards").Delete(&user)

// delete user's has one/many/many2many relations when deleting user
db.Select(clause.Associations).Delete(&user)

// delete user's account when deleting users
db.Select("Account").Delete(&users)

重大變更

我們嘗試列出重大變更或編譯器無法偵測到的變更,如果您發現任何未列出的重大變更,請在此建立問題或提出 pull request

標籤

  • GORM V2 偏好以 camelCase 撰寫標籤名稱,snake_case 中的標籤不再運作,例如:auto_incrementunique_indexpolymorphic_valueembedded_prefix,請查看 模型標籤
  • 用於指定外來鍵的標籤已變更為 foreignKeyreferences,請查看 關聯標籤
  • 不支援 sql 標籤

資料表名稱

TableName 將不再允許動態資料表名稱,TableName 的結果將快取以供未來使用

func (User) TableName() string {
return "t_user"
}

請使用 Scopes 來處理動態資料表,例如

func UserTable(u *User) func(*gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Table("user_" + u.Role)
}
}

db.Scopes(UserTable(&user)).Create(&user)

建立和刪除資料表需要使用 Migrator

先前可以如下建立和刪除資料表

db.CreateTable(&MyTable{})
db.DropTable(&MyTable{})

現在您執行下列動作

db.Migrator().CreateTable(&MyTable{})
db.Migrator().DropTable(&MyTable{})

Foreign Keys

新增外來鍵約束的方式為;

db.Model(&MyTable{}).AddForeignKey("profile_id", "profiles(id)", "NO ACTION", "NO ACTION")

現在您新增約束如下

db.Migrator().CreateConstraint(&Users{}, "Profiles")
db.Migrator().CreateConstraint(&Users{}, "fk_users_profiles")

轉換成以下的 postgres sql 程式碼

ALTER TABLE `Profiles` ADD CONSTRAINT `fk_users_profiles` FORIEGN KEY (`useres_id`) REFRENCES `users`(`id`))

Method Chain Safety/Goroutine Safety

為了減少 GC 分配,GORM V2 在使用方法鏈時會共用 Statement,並且只會為新的初始化 *gorm.DB 或在 New Session Method 之後建立新的 Statement 實例,若要重複使用 *gorm.DB,您需要確定它在 New Session Method 之後,例如

db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})

// Safe for new initialized *gorm.DB
for i := 0; i < 100; i++ {
go db.Where(...).First(&user)
}

tx := db.Where("name = ?", "jinzhu")
// NOT Safe as reusing Statement
for i := 0; i < 100; i++ {
go tx.Where(...).First(&user)
}

ctxDB := db.WithContext(ctx)
// Safe after a `New Session Method`
for i := 0; i < 100; i++ {
go ctxDB.Where(...).First(&user)
}

ctxDB := db.Where("name = ?", "jinzhu").WithContext(ctx)
// Safe after a `New Session Method`
for i := 0; i < 100; i++ {
go ctxDB.Where(...).First(&user) // `name = 'jinzhu'` will apply to the query
}

tx := db.Where("name = ?", "jinzhu").Session(&gorm.Session{})
// Safe after a `New Session Method`
for i := 0; i < 100; i++ {
go tx.Where(...).First(&user) // `name = 'jinzhu'` will apply to the query
}

查看 Method Chain 以取得詳細資料

Default Value

GORM V2 不會在建立後自動重新載入資料庫函數建立的預設值,請查看 Default Values 以取得詳細資料

Soft Delete

如果模型有一個名為 DeletedAt 的欄位,GORM V1 會啟用軟刪除,在 V2 中,您需要使用 gorm.DeletedAt,模型才會啟用此功能,例如

type User struct {
ID uint
DeletedAt gorm.DeletedAt
}

type User struct {
ID uint
// field with different name
Deleted gorm.DeletedAt
}

注意: gorm.Model 正在使用 gorm.DeletedAt,如果您正在嵌入它,則不需要變更任何內容

BlockGlobalUpdate

GORM V2 預設啟用 BlockGlobalUpdate 模式,若要觸發全域更新/刪除,您必須使用某些條件或使用原始 SQL 或啟用 AllowGlobalUpdate 模式,例如

db.Where("1 = 1").Delete(&User{})

db.Raw("delete from users")

db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&User{})

ErrRecordNotFound

當您使用預期會傳回一些結果的方法 FirstLastTake 查詢時,GORM V2 只會傳回 ErrRecordNotFound,而且我們也在 V2 中移除了方法 RecordNotFound,請使用 errors.Is 來檢查錯誤,例如

err := db.First(&user).Error
errors.Is(err, gorm.ErrRecordNotFound)

Hooks Method

在 V2 中,Before/After Create/Update/Save/Find/Delete 必須定義為類型為 func(tx *gorm.DB) error 的方法,它具有像外掛程式回呼等統一的介面,如果定義為其他類型,會列印警告記錄,而且不會生效,請查看 Hooks 以取得詳細資料

func (user *User) BeforeCreate(tx *gorm.DB) error {
// Modify current operation through tx.Statement, e.g:
tx.Statement.Select("Name", "Age")
tx.Statement.AddClause(clause.OnConflict{DoNothing: true})

// Operations based on tx will runs inside same transaction without clauses of current one
var role Role
err := tx.First(&role, "name = ?", user.Role).Error
// SELECT * FROM roles WHERE name = "admin"
return err
}

Update Hooks 支援 Changed 來檢查欄位是否已變更

使用 UpdateUpdates 進行更新時,可以在 Hooks 的 BeforeUpdateBeforeSave 中使用 Changed 方法檢查欄位是否已變更

func (user *User) BeforeUpdate(tx *gorm.DB) error {
if tx.Statement.Changed("Name", "Admin") { // if Name or Admin changed
tx.Statement.SetColumn("Age", 18)
}

if tx.Statement.Changed() { // if any fields changed
tx.Statement.SetColumn("Age", 18)
}
return nil
}

db.Model(&user).Update("Name", "Jinzhu") // update field `Name` to `Jinzhu`
db.Model(&user).Updates(map[string]interface{}{"name": "Jinzhu", "admin": false}) // update field `Name` to `Jinzhu`, `Admin` to false
db.Model(&user).Updates(User{Name: "Jinzhu", Admin: false}) // Update none zero fields when using struct as argument, will only update `Name` to `Jinzhu`

db.Model(&user).Select("Name", "Admin").Updates(User{Name: "Jinzhu"}) // update selected fields `Name`, `Admin`,`Admin` will be updated to zero value (false)
db.Model(&user).Select("Name", "Admin").Updates(map[string]interface{}{"Name": "Jinzhu"}) // update selected fields exists in the map, will only update field `Name` to `Jinzhu`

// Attention: `Changed` will only check the field value of `Update` / `Updates` equals `Model`'s field value, it returns true if not equal and the field will be saved
db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(map[string]interface{"name": "jinzhu2"}) // Changed("Name") => true
db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(map[string]interface{"name": "jinzhu"}) // Changed("Name") => false, `Name` not changed
db.Model(&User{ID: 1, Name: "jinzhu"}).Select("Admin").Updates(map[string]interface{"name": "jinzhu2", "admin": false}) // Changed("Name") => false, `Name` not selected to update

db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(User{Name: "jinzhu2"}) // Changed("Name") => true
db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(User{Name: "jinzhu"}) // Changed("Name") => false, `Name` not changed
db.Model(&User{ID: 1, Name: "jinzhu"}).Select("Admin").Updates(User{Name: "jinzhu2"}) // Changed("Name") => false, `Name` not selected to update

外掛程式

外掛程式回呼也需要定義為 func(tx *gorm.DB) error 型別的方法,有關詳細資訊,請參閱 撰寫外掛程式

使用結構體進行更新

使用結構體進行更新時,GORM V2 允許使用 Select 選擇零值欄位進行更新,例如

db.Model(&user).Select("Role", "Age").Update(User{Name: "jinzhu", Role: "", Age: 0})

關聯

GORM V1 允許使用一些設定來略過建立/更新關聯,在 V2 中,可以使用 Select 來執行這項工作,例如

db.Omit(clause.Associations).Create(&user)
db.Omit(clause.Associations).Save(&user)

db.Select("Company").Save(&user)

而且 GORM V2 不再允許使用 Set("gorm:auto_preload", true) 進行預載入,可以使用 Preload 搭配 clause.Associations,例如

// preload all associations
db.Preload(clause.Associations).Find(&users)

此外,請查看欄位權限,可用於略過建立/更新關聯

GORM V2 會在建立/更新記錄時使用 upsert 來儲存關聯,不再儲存完整的關聯資料,以保護您的資料免於儲存未完成的資料,例如

user := User{
Name: "jinzhu",
BillingAddress: Address{Address1: "Billing Address - Address 1"},
ShippingAddress: Address{Address1: "Shipping Address - Address 1"},
Emails: []Email{
{Email: "jinzhu@example.com"},
{Email: "jinzhu-2@example.com"},
},
Languages: []Language{
{Name: "ZH"},
{Name: "EN"},
},
}

db.Create(&user)
// BEGIN TRANSACTION;
// INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY DO NOTHING;
// INSERT INTO "users" (name,billing_address_id,shipping_address_id) VALUES ("jinzhu", 1, 2);
// INSERT INTO "emails" (user_id,email) VALUES (111, "jinzhu@example.com"), (111, "jinzhu-2@example.com") ON DUPLICATE KEY DO NOTHING;
// INSERT INTO "languages" ("name") VALUES ('ZH'), ('EN') ON DUPLICATE KEY DO NOTHING;
// INSERT INTO "user_languages" ("user_id","language_id") VALUES (111, 1), (111, 2) ON DUPLICATE KEY DO NOTHING;
// COMMIT;

關聯表格

在 GORM V2 中,JoinTable 可以是一個功能齊全的模型,具備 Soft DeleteHooks 等功能,並定義其他欄位,例如

type Person struct {
ID int
Name string
Addresses []Address `gorm:"many2many:person_addresses;"`
}

type Address struct {
ID uint
Name string
}

type PersonAddress struct {
PersonID int
AddressID int
CreatedAt time.Time
DeletedAt gorm.DeletedAt
}

func (PersonAddress) BeforeCreate(db *gorm.DB) error {
// ...
}

// PersonAddress must defined all required foreign keys, or it will raise error
err := db.SetupJoinTable(&Person{}, "Addresses", &PersonAddress{})

之後,您可以使用一般的 GORM 方法來操作關聯表格資料,例如

var results []PersonAddress
db.Where("person_id = ?", person.ID).Find(&results)

db.Where("address_id = ?", address.ID).Delete(&PersonAddress{})

db.Create(&PersonAddress{PersonID: person.ID, AddressID: address.ID})

計數

計數只接受 *int64 作為引數

交易

已移除一些交易方法,例如 RollbackUnlessCommitted,建議使用 Transaction 方法來包裝您的交易

db.Transaction(func(tx *gorm.DB) error {
// do some database operations in the transaction (use 'tx' from this point, not 'db')
if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
// return any error will rollback
return err
}

if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
return err
}

// return nil will commit the whole transaction
return nil
})

有關詳細資訊,請參閱 交易

遷移器

  • 遷移器預設會建立資料庫外來鍵
  • 遷移器更加獨立,許多 API 已重新命名,以提供更好的支援,並為每個資料庫提供統一的 API 介面
  • 如果大小、精度或是否可為 Null 的值變更,AutoMigrate 會變更欄位的型別
  • 透過標籤 check 支援檢查器
  • 針對 index 強化標籤設定

有關詳細資訊,請參閱 遷移

type UserIndex struct {
Name string `gorm:"check:named_checker,(name <> 'jinzhu')"`
Name2 string `gorm:"check:(age > 13)"`
Name4 string `gorm:"index"`
Name5 string `gorm:"index:idx_name,unique"`
Name6 string `gorm:"index:,sort:desc,collate:utf8,type:btree,length:10,where:name3 != 'jinzhu'"`
}

快樂駭客!

白金贊助商

金牌贊助商

白金贊助商

金牌贊助商