GO中GORM 使用教程
GORM 是一個(gè)用于 Go 語(yǔ)言的 ORM(Object-Relational Mapping) 庫(kù)。它將關(guān)系型數(shù)據(jù)庫(kù)的表與 Go 語(yǔ)言中的結(jié)構(gòu)體相映射,允許開(kāi)發(fā)者以面向?qū)ο蟮姆绞讲僮鲾?shù)據(jù)庫(kù),而不需要直接編寫(xiě) SQL 語(yǔ)句。通過(guò) GORM,開(kāi)發(fā)者可以利用 Go 語(yǔ)言的結(jié)構(gòu)體和方法來(lái)執(zhí)行常見(jiàn)的數(shù)據(jù)庫(kù)操作(如查詢、插入、更新、刪除等),大大簡(jiǎn)化了與數(shù)據(jù)庫(kù)的交互過(guò)程。
1. 安裝 GORM
首先,你需要安裝 GORM 庫(kù)。打開(kāi)終端并運(yùn)行以下命令:
go get -u gorm.io/gorm go get -u gorm.io/driver/sqlite # 示例數(shù)據(jù)庫(kù),可以根據(jù)需求更換為其他數(shù)據(jù)庫(kù)驅(qū)動(dòng)
2. 創(chuàng)建基礎(chǔ)結(jié)構(gòu)體
ORM的一個(gè)核心概念是結(jié)構(gòu)體,它代表數(shù)據(jù)庫(kù)表的一個(gè)映射。例如,假設(shè)你有一個(gè)“用戶”表,我們可以創(chuàng)建一個(gè) User
結(jié)構(gòu)體來(lái)表示它。
package main import ( "gorm.io/driver/sqlite" "gorm.io/gorm" ) type User struct { ID uint `gorm:"primaryKey"` // 主鍵 Name string `gorm:"size:100"` // 用戶名字段,限制長(zhǎng)度為100 Age uint // 用戶年齡 CreatedAt time.Time // 創(chuàng)建時(shí)間(GORM 會(huì)自動(dòng)管理) } func main() { // 創(chuàng)建數(shù)據(jù)庫(kù)連接 db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{}) if err != nil { panic("failed to connect to the database") } // 自動(dòng)遷移:通過(guò)GORM根據(jù)結(jié)構(gòu)體自動(dòng)創(chuàng)建表 db.AutoMigrate(&User{}) }
在 GORM 中,結(jié)構(gòu)體字段的標(biāo)簽(tags)用于定義和控制如何將 Go 結(jié)構(gòu)體映射到數(shù)據(jù)庫(kù)表的列。GORM 支持很多標(biāo)簽,可以配置數(shù)據(jù)庫(kù)表的列屬性、索引、關(guān)系等。以下是常見(jiàn)的 GORM 字段標(biāo)簽和它們的作用:
標(biāo)簽 | 描述 | 示例 |
---|---|---|
primaryKey | 指定字段為主鍵。 | gorm:"primaryKey" |
size | 指定字段的大小,通常用于字符串字段。 | gorm:"size:100" |
unique | 創(chuàng)建唯一索引,確保字段值唯一。 | gorm:"unique" |
not null | 指定字段不能為空。 | gorm:"not null" |
default | 指定字段的默認(rèn)值。 | gorm:"default:0" |
autoIncrement | 設(shè)置字段為自增字段。通常用于整數(shù)類型的主鍵。 | gorm:"autoIncrement" |
index | 為字段創(chuàng)建索引。 | gorm:"index" |
uniqueIndex | 為字段創(chuàng)建唯一索引。 | gorm:"uniqueIndex" |
index:<name> | 創(chuàng)建帶有自定義名稱的索引。 | gorm:"index:idx_name" |
foreignKey | 指定外鍵字段,在一對(duì)多或多對(duì)多關(guān)系中使用。 | gorm:"foreignKey:UserID" |
references | 指定外鍵關(guān)系中引用的字段。 | gorm:"references:ID" |
embedded | 嵌入一個(gè)結(jié)構(gòu)體,扁平化嵌入的結(jié)構(gòu)體字段到父結(jié)構(gòu)體中。 | gorm:"embedded" |
preload | 在查詢時(shí)預(yù)加載關(guān)聯(lián)數(shù)據(jù)。 | gorm:"preload" |
createdAt | 自動(dòng)生成的創(chuàng)建時(shí)間戳字段。 | gorm:"autoCreateTime" |
updatedAt | 自動(dòng)生成的更新時(shí)間戳字段。 | gorm:"autoUpdateTime" |
deletedAt | 自動(dòng)生成的刪除時(shí)間戳字段,支持軟刪除。 | gorm:"softDelete" |
softDelete | 用于支持軟刪除功能。通常與 DeletedAt 配合使用。 | gorm:"index" |
column | 指定數(shù)據(jù)庫(kù)中的列名,當(dāng)字段名與列名不一致時(shí)使用。 | gorm:"column:db_column_name" |
type | 指定字段在數(shù)據(jù)庫(kù)中的類型,通常用于特殊類型(如 JSON)。 | gorm:"type:text" |
many2many | 用于多對(duì)多關(guān)系,指定連接表的名稱。 | gorm:"many2many:user_posts" |
這個(gè)表格展示了 GORM 中常用的字段標(biāo)簽以及它們?nèi)绾斡绊憯?shù)據(jù)庫(kù)表結(jié)構(gòu),幫助開(kāi)發(fā)者更好地理解和使用 GORM 進(jìn)行數(shù)據(jù)庫(kù)操作。
3. 數(shù)據(jù)庫(kù)連接與配置
在上面的示例中,我們使用了 gorm.Open()
來(lái)連接 SQLite 數(shù)據(jù)庫(kù)。如果你使用 MySQL 或 Postgres 等其他數(shù)據(jù)庫(kù),可以更換相應(yīng)的驅(qū)動(dòng)。
例如,連接到 MySQL 數(shù)據(jù)庫(kù)的代碼如下:
db, err := gorm.Open(mysql.Open("user:password@tcp(localhost:3306)/dbname"), &gorm.Config{})
4. 數(shù)據(jù)庫(kù)操作
4.1 創(chuàng)建記錄
你可以使用 Create
方法來(lái)插入數(shù)據(jù)。下面是如何插入一個(gè)新用戶:
func createUser(db *gorm.DB) { user := User{Name: "John Doe", Age: 30} result := db.Create(&user) if result.Error != nil { panic("Failed to create user") } fmt.Println("User created:", user.ID) }
代碼中db.Create(&user)
使用 &
符號(hào)是因?yàn)槲覀円獋鬟f結(jié)構(gòu)體 user
的 指針 給 Create
方法。具體來(lái)說(shuō),這樣做有以下幾個(gè)原因:
指針傳遞可以修改原結(jié)構(gòu)體
GORM 的
Create
方法接受結(jié)構(gòu)體的 指針,這樣它可以直接修改原始結(jié)構(gòu)體的值,而不僅僅是副本。通過(guò)傳遞指針,GORM 能夠在插入數(shù)據(jù)庫(kù)的過(guò)程中修改結(jié)構(gòu)體(例如,給結(jié)構(gòu)體的字段賦值,例如數(shù)據(jù)庫(kù)自動(dòng)生成的ID
或CreatedAt
字段),確保結(jié)構(gòu)體反映數(shù)據(jù)庫(kù)中的最新數(shù)據(jù)。例如,
user
的ID
字段會(huì)在插入數(shù)據(jù)庫(kù)時(shí)由 GORM 自動(dòng)賦值(通常是自增的主鍵),如果你傳遞的是結(jié)構(gòu)體的指針,Create
方法可以直接更新user
結(jié)構(gòu)體中的ID
字段。避免復(fù)制大結(jié)構(gòu)體
如果你傳遞的是結(jié)構(gòu)體的副本,GORM 會(huì)先創(chuàng)建一個(gè)結(jié)構(gòu)體的拷貝并將其插入數(shù)據(jù)庫(kù)。這對(duì)于較大的結(jié)構(gòu)體來(lái)說(shuō)可能會(huì)浪費(fèi)內(nèi)存并降低性能。而傳遞指針避免了復(fù)制整個(gè)結(jié)構(gòu)體,只是傳遞了結(jié)構(gòu)體的內(nèi)存地址,性能更高。
GORM 的工作方式
GORM 內(nèi)部使用了指針來(lái)標(biāo)識(shí)結(jié)構(gòu)體字段的變化。通過(guò)傳遞指針,GORM 可以確定結(jié)構(gòu)體的變化并進(jìn)行相應(yīng)的處理。例如,在執(zhí)行
Create
時(shí),GORM 會(huì)檢查結(jié)構(gòu)體的指針,判斷該字段是否已經(jīng)賦值、是否需要自動(dòng)填充等。
4.2 查詢記錄
GORM 提供了多種查詢方式,可以通過(guò)結(jié)構(gòu)體查詢、條件查詢等方式來(lái)獲取數(shù)據(jù)。
獲取單條記錄
func getUser(db *gorm.DB) { var user User result := db.First(&user, 1) // 查找 user 表中主鍵為 1 的記錄,并將其填充到 user 結(jié)構(gòu)體中 if result.Error != nil { panic("User not found") } fmt.Println("User:", user) }
db.First
是 GORM 提供的一個(gè)查詢方法,用于從數(shù)據(jù)庫(kù)中獲取 第一條 滿足條件的記錄。它通常用于根據(jù)主鍵或其他條件查詢數(shù)據(jù)。
db.First
的基本語(yǔ)法:
db.First(&model, conditions...)
&model
是一個(gè)指針參數(shù),表示查詢的結(jié)果將會(huì)填充到這個(gè)結(jié)構(gòu)體中。conditions...
是查詢的條件,可以是主鍵或其他字段。
如果查詢成功,db.First
會(huì)把查詢到的記錄填充到 model
指針?biāo)赶虻慕Y(jié)構(gòu)體里。如果沒(méi)有找到記錄,它會(huì)返回一個(gè)錯(cuò)誤。
在 db.First(&user, 1)
中,&user
是指向 user
結(jié)構(gòu)體的指針。這里傳遞指針是因?yàn)?GORM 要修改 user
結(jié)構(gòu)體的值(即填充查詢結(jié)果)。
- 通過(guò)傳遞結(jié)構(gòu)體的指針,GORM 可以將查詢結(jié)果直接賦值到
user
結(jié)構(gòu)體中。 - 如果你傳遞的是結(jié)構(gòu)體本身,而不是指針,查詢結(jié)果將不會(huì)填充到結(jié)構(gòu)體中,因?yàn)榻Y(jié)構(gòu)體會(huì)作為副本傳遞到
db.First
方法,而 GORM 需要能夠修改原始結(jié)構(gòu)體的字段值。
獲取多個(gè)記錄
func getUsers(db *gorm.DB) { var users []User result := db.Find(&users) if result.Error != nil { panic("No users found") } fmt.Println("Users:", users) }
db.Find
是 GORM 提供的查詢方法之一,用于查找多個(gè)記錄并將其存儲(chǔ)到傳入的切片結(jié)構(gòu)體中。
Find
方法會(huì)根據(jù)傳入的條件來(lái)查找記錄,可以是簡(jiǎn)單的查詢(如所有記錄),也可以是有條件的查詢(如按字段值過(guò)濾)。- 傳遞給
Find
的參數(shù)是一個(gè)指針,它會(huì)將查詢到的記錄填充到指向的切片中。
db.Find(&users)
會(huì)從數(shù)據(jù)庫(kù)中查找所有記錄(或者根據(jù)傳入的查詢條件查找記錄)并將它們填充到 users
切片中。查詢的結(jié)果會(huì)是一個(gè)結(jié)構(gòu)體的集合。Find 方法默認(rèn)返回所有滿足條件的記錄。
- 如果查詢沒(méi)有條件,
Find
將返回?cái)?shù)據(jù)庫(kù)表中的所有記錄。 - 如果你傳遞了查詢條件,
Find
將根據(jù)條件過(guò)濾結(jié)果。
Find 方法的其他功能
- 查詢條件:你可以通過(guò)傳遞查詢條件來(lái)限制查詢的結(jié)果。例如,如果你想查找年齡大于 30 的所有用戶,可以這么寫(xiě):
db.Find(&users, "age > ?", 30)
這個(gè)查詢會(huì)返回所有年齡大于 30 的用戶。
- 分頁(yè):
Find
還支持分頁(yè)查詢。你可以通過(guò)Limit
和Offset
方法來(lái)實(shí)現(xiàn)分頁(yè)查詢。例如,查詢前 10 條記錄:
db.Limit(10).Find(&users)
- 排序:你也可以通過(guò)
Order
方法來(lái)指定查詢結(jié)果的排序方式。例如,按年齡排序:
db.Order("age desc").Find(&users)
- 返回記錄數(shù):
Find
方法還會(huì)返回查詢的結(jié)果,包括查詢到的記錄數(shù)。如果沒(méi)有記錄,它會(huì)返回一個(gè)空的切片。
4.3 更新記錄
在 GORM 中,更新記錄是一個(gè)常見(jiàn)的操作。你可以通過(guò) GORM 提供的幾種方法來(lái)更新記錄。以下將詳細(xì)介紹 GORM 中更新記錄的方式,包含基本更新、部分更新、批量更新等操作,并解釋每種方法的具體用法和注意事項(xiàng)。
基本更新:db.Save 方法
db.Save
方法用于保存(或更新)結(jié)構(gòu)體中的數(shù)據(jù)。如果結(jié)構(gòu)體的主鍵已經(jīng)存在,GORM 會(huì)執(zhí)行 更新操作;如果主鍵不存在,GORM 會(huì)執(zhí)行 插入操作(也稱為 “upsert”)。因此,db.Save
不僅適用于更新已有記錄,也適用于插入新記錄。
示例
func main() { var user User db.First(&user, 1) // 查找主鍵為 1 的用戶 user.Name = "Alice Updated" // 修改字段 user.Age = 30 db.Save(&user) // 更新記錄 }
db.Save(&user)
會(huì)檢查user
是否已有主鍵值(假設(shè)主鍵存在)。如果存在,它將執(zhí)行更新操作,將user
結(jié)構(gòu)體中修改的字段更新到數(shù)據(jù)庫(kù)中。- 如果主鍵不存在,它會(huì)將
user
插入到數(shù)據(jù)庫(kù)中。
注意:
Save
會(huì)更新所有非零字段(即結(jié)構(gòu)體中的字段如果是空值,可能不會(huì)被更新),并且會(huì)更新所有字段,即使你沒(méi)有顯式修改某個(gè)字段。- 如果你希望只更新某些字段,應(yīng)該使用
Updates
或Update
方法。
更新部分字段:db.Updates 方法
db.Updates
方法允許你更新結(jié)構(gòu)體中的 部分字段,而不是全部字段。它是一個(gè)更精確的更新方法,通常用于僅更新結(jié)構(gòu)體中某些修改了的字段。
示例
func main() { var user User db.First(&user, 1) // 查找主鍵為 1 的用戶 db.Model(&user).Updates(User{Name: "Bob Updated", Age: 35}) }
在這個(gè)例子中:
db.Model(&user).Updates(User{Name: "Bob Updated", Age: 35})
只會(huì)更新user
結(jié)構(gòu)體中的Name
和Age
字段。db.Model(&user)
表明更新的是user
結(jié)構(gòu)體對(duì)應(yīng)的數(shù)據(jù)庫(kù)記錄。Updates
方法中的參數(shù)可以是一個(gè)結(jié)構(gòu)體(如User{Name: "Bob Updated"}
),也可以是一個(gè)map[string]interface{}
(鍵是字段名,值是要更新的值)。
注意:
Updates
會(huì)忽略零值字段(如空字符串、零整數(shù)等),如果某個(gè)字段的值為零,它不會(huì)被更新。db.Model(&user)
用于指定要更新的模型或表。Updates
會(huì)將修改過(guò)的字段進(jìn)行更新,但不會(huì)更新模型中未指定的字段。
單個(gè)字段更新:db.Update 方法
如果你只需要更新某個(gè)單獨(dú)的字段,可以使用 db.Update
方法。該方法用于 更新單個(gè)字段,是 db.Updates
的簡(jiǎn)化版本,適合只更新單一字段的場(chǎng)景。
示例
func main() { var user User db.First(&user, 1) // 查找主鍵為 1 的用戶 db.Model(&user).Update("Age", 40) // 只更新 Age 字段 }
db.Model(&user).Update("Age", 40)
會(huì)將Age
字段更新為40
,其他字段保持不變。Update
方法適用于你只需要更新單個(gè)字段的情況。
注意:
Update
方法只更新指定的字段,不會(huì)影響其他字段。- 如果字段的值為零值,
Update
也會(huì)更新該字段(沒(méi)有零值忽略
的機(jī)制)。
批量更新:db.UpdateColumn 和 db.UpdateColumns
GORM 還提供了 UpdateColumn
和 UpdateColumns
方法,主要用于 批量更新 字段。這些方法與 Update
方法類似,但它們不會(huì)觸發(fā) GORM 的鉤子(如 BeforeSave
、AfterSave
等)。
UpdateColumn 示例
db.Model(&user).UpdateColumn("Age", 45)
UpdateColumn
不會(huì)觸發(fā) GORM 的 BeforeSave
和 AfterSave
鉤子,因此適用于需要繞過(guò)這些鉤子的情況。
UpdateColumns 示例
db.Model(&user).UpdateColumns(map[string]interface{}{"Age": 50, "Name": "Charlie Updated"})
UpdateColumns
會(huì)根據(jù)傳入的字段進(jìn)行批量更新。與 Update
不同,它不會(huì)觸發(fā)模型的鉤子。
注意:
- 這兩個(gè)方法直接更新字段,不會(huì)對(duì)字段的零值進(jìn)行忽略。
- 它們只進(jìn)行 單字段的原子更新,不會(huì)涉及到多表關(guān)聯(lián)等操作。
條件更新:db.Where 和 db.Updates
你可以在更新時(shí)通過(guò) Where
方法指定更新的條件。Where
方法可以與 Updates
或 Update
一起使用,以便進(jìn)行條件更新。
示例
db.Model(&User{}).Where("age > ?", 30).Updates(User{Name: "Updated Name"})
- 這個(gè)示例中,
Where("age > ?", 30)
限定了更新條件,只有年齡大于 30 的用戶才會(huì)被更新。 Updates(User{Name: "Updated Name"})
更新所有符合條件的用戶的Name
字段。
注意:
Where
可以幫助你構(gòu)造復(fù)雜的更新條件,但也可以根據(jù)需要單獨(dú)使用(例如,按 ID 更新某些記錄)。
批量更新(多個(gè)記錄)
你可以使用 db.Model()
方法和 db.Updates()
方法來(lái)批量更新多個(gè)記錄。下面是一個(gè)批量更新的示例:
示例
db.Model(&User{}).Where("age > ?", 30).Updates(User{Name: "Batch Update"})
- 這個(gè)例子會(huì)更新所有
age > 30
的用戶,將它們的Name
字段修改為"Batch Update"
。
注意:
Updates
會(huì)更新所有符合條件的記錄,而不是只更新一個(gè)記錄。
使用事務(wù)更新多個(gè)記錄
如果你需要確保多個(gè)更新操作的原子性,可以將更新操作放入一個(gè)事務(wù)中。在 GORM 中,事務(wù)通過(guò) db.Begin()
開(kāi)始,db.Commit()
提交,db.Rollback()
回滾。
示例
tx := db.Begin() // 執(zhí)行多個(gè)更新操作 tx.Model(&User{}).Where("age > ?", 30).Updates(User{Name: "Transactional Update"}) tx.Model(&User{}).Where("name = ?", "Bob").Update("Age", 40) if err := tx.Commit().Error; err != nil { tx.Rollback() fmt.Println("Error:", err) return }
db.Begin()
開(kāi)始一個(gè)事務(wù)。tx.Commit()
提交事務(wù),tx.Rollback()
在出錯(cuò)時(shí)回滾事務(wù),確保所有操作的原子性。
4.4 刪除記錄
刪除記錄可以使用 Delete
方法:
func deleteUser(db *gorm.DB) { var user User db.First(&user, 1) // 查找要?jiǎng)h除的用戶 // 刪除用戶 result := db.Delete(&user) if result.Error != nil { panic("Failed to delete user") } fmt.Println("User deleted:", user.ID) }
5. 關(guān)系與關(guān)聯(lián)查詢
GORM 支持表之間的關(guān)系映射。比如,我們有 User
和 Post
之間的關(guān)系。一個(gè)用戶可以有多個(gè)帖子,可以使用 has many
關(guān)系。
5.1 定義關(guān)聯(lián)結(jié)構(gòu)體
type Post struct { ID uint Title string Body string UserID uint // 外鍵 User User // 關(guān)聯(lián)的 User }
5.2 關(guān)聯(lián)查詢
假設(shè)我們有 User
和 Post
兩個(gè)表,你可以使用 Preload
來(lái)加載關(guān)聯(lián)的 Post
數(shù)據(jù)。
func getUserWithPosts(db *gorm.DB) { var user User db.Preload("Posts").First(&user, 1) fmt.Println("User:", user.Name) fmt.Println("Posts:", user.Posts) }
5.3 創(chuàng)建關(guān)聯(lián)記錄
當(dāng)你插入一個(gè)帶有關(guān)聯(lián)的記錄時(shí),可以使用 Create
方法來(lái)同時(shí)插入主表和從表數(shù)據(jù):
func createUserWithPosts(db *gorm.DB) { user := User{Name: "Alice", Age: 28, Posts: []Post{ {Title: "Post 1", Body: "This is the first post"}, {Title: "Post 2", Body: "This is the second post"}, }} db.Create(&user) fmt.Println("User and Posts created:", user) }
6. 事務(wù)
在 GORM 中,事務(wù)(Transaction) 是一個(gè)非常重要的概念,尤其是在需要確保多個(gè)數(shù)據(jù)庫(kù)操作要么全部成功,要么全部失敗的情況下。事務(wù)能夠保證操作的原子性、一致性、隔離性和持久性(即 ACID 特性)。如果在事務(wù)中的某個(gè)操作失敗,事務(wù)可以回滾,使得數(shù)據(jù)庫(kù)回到事務(wù)開(kāi)始之前的狀態(tài)。
事務(wù)(Transaction)是一組數(shù)據(jù)庫(kù)操作,它們要么全部執(zhí)行,要么在發(fā)生錯(cuò)誤時(shí)全部不執(zhí)行。事務(wù)在數(shù)據(jù)庫(kù)操作中提供了 原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation) 和 持久性(Durability)(合稱為ACID特性):
- 原子性(Atomicity):事務(wù)中的所有操作要么都執(zhí)行,要么都不執(zhí)行。即事務(wù)是不可分割的整體。
- 一致性(Consistency):事務(wù)執(zhí)行前后,數(shù)據(jù)庫(kù)的狀態(tài)必須是一致的,符合數(shù)據(jù)庫(kù)的完整性約束。
- 隔離性(Isolation):一個(gè)事務(wù)的執(zhí)行不會(huì)被其他事務(wù)干擾。事務(wù)的執(zhí)行是相互隔離的。
- 持久性(Durability):一旦事務(wù)提交,其對(duì)數(shù)據(jù)庫(kù)的更改是永久性的,不會(huì)丟失。
在 GORM 中,你可以通過(guò) db.Begin()
來(lái)啟動(dòng)一個(gè)事務(wù),使用 tx.Commit()
來(lái)提交事務(wù),使用 tx.Rollback()
來(lái)回滾事務(wù)。以下是 GORM 中事務(wù)的常見(jiàn)用法。
6.1. 開(kāi)始事務(wù):db.Begin()
你可以通過(guò) db.Begin()
啟動(dòng)一個(gè)事務(wù)。這個(gè)方法會(huì)返回一個(gè)事務(wù)對(duì)象(*gorm.DB
類型),通過(guò)這個(gè)對(duì)象你可以執(zhí)行數(shù)據(jù)庫(kù)操作。
tx := db.Begin() // 開(kāi)始事務(wù)
db.Begin()
會(huì)創(chuàng)建一個(gè)事務(wù)。- 返回的
tx
是事務(wù)對(duì)象,所有的數(shù)據(jù)庫(kù)操作都應(yīng)通過(guò)tx
來(lái)執(zhí)行,而不是直接使用db
。
6.2 執(zhí)行事務(wù)中的操作
在事務(wù)中,你可以進(jìn)行一系列的數(shù)據(jù)庫(kù)操作。所有的操作都應(yīng)該通過(guò)事務(wù)對(duì)象 tx
來(lái)執(zhí)行,而不是直接通過(guò) db
執(zhí)行。
示例
tx := db.Begin() // 開(kāi)始事務(wù) // 執(zhí)行多個(gè)數(shù)據(jù)庫(kù)操作 if err := tx.Create(&user).Error; err != nil { tx.Rollback() // 操作失敗,回滾事務(wù) return err } if err := tx.Model(&user).Update("Age", 30).Error; err != nil { tx.Rollback() // 操作失敗,回滾事務(wù) return err }
tx.Create(&user)
會(huì)在事務(wù)中插入一條記錄。tx.Model(&user).Update("Age", 30)
會(huì)在事務(wù)中更新該記錄。
6.3 提交事務(wù):tx.Commit()
當(dāng)所有的操作都執(zhí)行成功時(shí),可以調(diào)用 tx.Commit()
提交事務(wù),將所有的變更永久保存到數(shù)據(jù)庫(kù)。
if err := tx.Commit().Error; err != nil { tx.Rollback() // 提交失敗,回滾事務(wù) return err }
tx.Commit()
會(huì)提交事務(wù),執(zhí)行所有的操作并將它們持久化到數(shù)據(jù)庫(kù)。
6.4 回滾事務(wù):tx.Rollback()
如果在事務(wù)過(guò)程中遇到錯(cuò)誤,應(yīng)該調(diào)用 tx.Rollback()
來(lái)回滾事務(wù)。這樣,所有在事務(wù)中執(zhí)行的操作都會(huì)撤銷,數(shù)據(jù)庫(kù)將恢復(fù)到事務(wù)開(kāi)始前的狀態(tài)。
if err := tx.Rollback().Error; err != nil { fmt.Println("Error during rollback:", err) return err }
tx.Rollback()
會(huì)撤銷事務(wù)中的所有操作。
6.5 在事務(wù)中使用錯(cuò)誤處理
通常,事務(wù)中的操作需要進(jìn)行錯(cuò)誤處理。只要有任何一項(xiàng)操作失敗,應(yīng)該調(diào)用 tx.Rollback()
進(jìn)行回滾。
tx := db.Begin() // 執(zhí)行操作 1 if err := tx.Create(&user).Error; err != nil { tx.Rollback() // 錯(cuò)誤發(fā)生,回滾事務(wù) return err } // 執(zhí)行操作 2 if err := tx.Model(&user).Update("Age", 30).Error; err != nil { tx.Rollback() // 錯(cuò)誤發(fā)生,回滾事務(wù) return err } // 所有操作成功,提交事務(wù) if err := tx.Commit().Error; err != nil { tx.Rollback() // 提交失敗,回滾事務(wù) return err }
6.6 事務(wù)中的多表操作
在事務(wù)中,你可以操作多個(gè)表,只要使用同一個(gè)事務(wù)對(duì)象 tx
,所有的表操作都將在一個(gè)事務(wù)內(nèi)完成。
示例:多表操作
tx := db.Begin() // 插入用戶表 if err := tx.Create(&user).Error; err != nil { tx.Rollback() return err } // 更新訂單表 if err := tx.Model(&order).Update("Status", "Shipped").Error; err != nil { tx.Rollback() return err } // 提交事務(wù) if err := tx.Commit().Error; err != nil { tx.Rollback() return err }
- 這里插入了一個(gè)用戶并更新了訂單狀態(tài),所有操作都在同一個(gè)事務(wù)中進(jìn)行。
6.7 事務(wù)的嵌套
GORM 不直接支持嵌套事務(wù)(即在一個(gè)事務(wù)中開(kāi)啟另一個(gè)事務(wù))。但是,你可以通過(guò)手動(dòng)管理事務(wù)嵌套。在嵌套事務(wù)中,只有最外層的事務(wù)會(huì)決定是否提交或回滾。
tx := db.Begin() // 外部事務(wù)操作 if err := tx.Create(&user).Error; err != nil { tx.Rollback() return err } nestedTx := tx.Begin() // 開(kāi)始嵌套事務(wù) // 嵌套事務(wù)操作 if err := nestedTx.Model(&order).Update("Status", "Shipped").Error; err != nil { nestedTx.Rollback() // 嵌套事務(wù)回滾 tx.Rollback() // 外部事務(wù)回滾 return err } nestedTx.Commit() // 嵌套事務(wù)提交 tx.Commit() // 外部事務(wù)提交
- 上述代碼演示了如何在一個(gè)事務(wù)中手動(dòng)開(kāi)啟一個(gè)嵌套事務(wù)。嵌套事務(wù)的提交和回滾會(huì)影響最外層事務(wù)。
6.8 事務(wù)中的并發(fā)問(wèn)題
在事務(wù)中使用并發(fā)操作時(shí),必須小心并發(fā)引起的 數(shù)據(jù)競(jìng)爭(zhēng) 和 死鎖 問(wèn)題。GORM 默認(rèn)使用 隔離級(jí)別 為 ReadCommitted
,你可以通過(guò)配置數(shù)據(jù)庫(kù)的事務(wù)隔離級(jí)別來(lái)避免一些并發(fā)問(wèn)題。
tx := db.Begin().Set("gorm:query_option", "LOCK IN SHARE MODE") // 事務(wù)操作
此時(shí),LOCK IN SHARE MODE
會(huì)在查詢時(shí)加鎖,避免其他事務(wù)修改同一行數(shù)據(jù),防止數(shù)據(jù)不一致。
總結(jié)
GORM 是一個(gè)功能強(qiáng)大且易于使用的 Go 語(yǔ)言 ORM 庫(kù),能夠讓開(kāi)發(fā)者以面向?qū)ο蟮姆绞脚c數(shù)據(jù)庫(kù)交互,減少了 SQL 語(yǔ)句的編寫(xiě)和管理的復(fù)雜度。它適合需要處理數(shù)據(jù)庫(kù)的 Go 項(xiàng)目,特別是那些涉及大量數(shù)據(jù)操作、需要事務(wù)支持和多表關(guān)聯(lián)的應(yīng)用。
到此這篇關(guān)于GO中GORM 使用教程的文章就介紹到這了,更多相關(guān)GO GORM 使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go實(shí)現(xiàn)自動(dòng)解壓縮包以及讀取docx/doc文件內(nèi)容詳解
在開(kāi)發(fā)過(guò)程中,我們常常需要處理壓縮包和文檔文件。本文將介紹如何使用Go語(yǔ)言自動(dòng)解壓縮包和讀取docx/doc文件,需要的可以參考一下2023-03-03Golang設(shè)計(jì)模式中的橋接模式詳細(xì)講解
橋接模式是一種結(jié)構(gòu)型設(shè)計(jì)模式,通過(guò)橋接模式可以將抽象部分和它的實(shí)現(xiàn)部分分離,本文主要介紹了GoLang橋接模式,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2023-01-01Golang使用singleflight解決并發(fā)重復(fù)請(qǐng)求
高并發(fā)的場(chǎng)景下,經(jīng)常會(huì)出現(xiàn)并發(fā)重復(fù)請(qǐng)求資源的情況,singleflight是golang內(nèi)置的一個(gè)包,這個(gè)包提供了對(duì)重復(fù)函數(shù)調(diào)用的抑制功能,所以下面我們就來(lái)看看如何使用它解決并發(fā)重復(fù)請(qǐng)求吧2023-08-08Go程序的init函數(shù)在什么時(shí)候執(zhí)行
在Go語(yǔ)言中,init?函數(shù)是一個(gè)特殊的函數(shù),它用于執(zhí)行程序的初始化任務(wù),本文主要介紹了Go程序的init函數(shù)在什么時(shí)候執(zhí)行,感興趣的可以了解一下2023-10-10go將request?body綁定到不同的結(jié)構(gòu)體中教程
這篇文章主要為大家介紹了go將request?body綁定到不同的結(jié)構(gòu)體中教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10Ubuntu18.04 LTS搭建GO語(yǔ)言開(kāi)發(fā)環(huán)境過(guò)程解析
這篇文章主要介紹了Ubuntu18.04 LTS搭建GO語(yǔ)言開(kāi)發(fā)環(huán)境過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11