前言
本來是想著寫多表關系的,不過寫了一半發現重復的部分太多了,想了想與其做一些重復性工作,不如把一些當時覺得抽象的東西記錄一下,就當用一篇雜記完成專欄的最后一篇文章吧。
預加載
簡單示例
預加載主要用于在多表關系中加載關聯表的信息,在講解預加載的類型之前我們先來看一個預加載的示例:
- 相關表結構
type User struct {gorm.ModelName stringLanguages []Language `gorm:"many2many:user_languages;"`
}type Language struct {gorm.ModelName stringUsers []User `gorm:"many2many:user_languages;"`
}
我們嘗試往里面插入數據:
// 創建語言對象languages := []Language{{Name: "Golang"},{Name: "Python"},{Name: "Java"},}// 創建用戶對象users := []User{{Name: "Alice", Languages: []Language{languages[0], languages[1]}}, // Alice 會說 Golang 和 Python{Name: "Bob", Languages: []Language{languages[1], languages[2]}}, // Bob 會說 Python 和 Java{Name: "Charlie", Languages: []Language{languages[0], languages[2]}}, // Charlie 會說 Golang 和 Java}// 將語言和用戶數據插入到數據庫中for _, lang := range languages {db.Create(&lang)}for _, user := range users {db.Create(&user)}
然后我們嘗試利用預加載來查詢:
users := []User{}db.Preload("Languages").Find(&users)fmt.Println(users)
這樣我們不僅能搜尋到user
,還能把user
相關聯的Languages
打印出來,像這樣:
Joins預加載
Joins
預加載會使用left join加載關聯數據,與其說是預加載其實更像一個關聯查詢,常用與ONE TO ONE
或Belongs To
的多表關系中:
- 表結構:
type Student struct {ID uint `gorm:"size:8"`Name string `gorm:"size:20"`Info StudentInfo `gorm:"foreignKey:StudentID"` // 明確指定關聯關系
}type StudentInfo struct {ID uint `gorm:"size:8"`Age int `gorm:"size:4"`Sex bool `gorm:"size:4"`Email *stringStudentID uint `gorm:"size:8"`
}
- 示例:
var student Studentdb.Joins("Info").Take(&student)db.Joins("Info", db.Where("Info.Age = ?", 18)) //帶條件的Joinsfmt.Println(student)
條件預加載
var student Studentdb.Where("age > ?", 18).Preload("Info").First(&student) //方式一db.Preload("Info", "age > ?", 18).Find(&student) //方式二fmt.Println(student)
自定義預加載
var student Studentdb.Preload("Info", func(db *gorm.DB) *gorm.DB{return db.Where("age > ?", 18)}).Find(&student)fmt.Println(student)
嵌套預加載
這里我們來看一下官方給的示例:
// Customize Preload conditions for `Orders`
// And GORM won't preload unmatched order's OrderItems then
db.Preload("Orders", "state = ?", "paid").Preload("Orders.OrderItems").Find(&users)
這段代碼的意思是,在加載用戶信息時,只預加載訂單狀態為 “paid” 的訂單數據,并且同時預加載這些訂單的訂單項信息。這樣做可以確保在查詢用戶數據時,只加載特定狀態的訂單及其訂單項數據,而不會加載其他狀態的訂單信息。
#關聯標簽與多態關聯
多態關聯
關于多態關聯我們先來看一個實例:
package mainimport ("gorm.io/gorm""gorm/ConnectDB"
)type Boy struct {gorm.ModelName stringToys []Toy `gorm:"polymorphic:Owner"`
}type Girl struct {gorm.ModelName stringToys []Toy `gorm:"polymorphic:Owner"`
}type Toy struct {gorm.ModelName stringOwnerID uintOwnerType string
}func main() {db, err := ConnectDB.Connect()if err != nil {panic(err)}err = db.AutoMigrate(&Boy{}, &Girl{}, &Toy{})if err != nil {panic(err)}db.Create(&Boy{Name: "張三",Toys: []Toy{{Name: "玩具1"},{Name: "玩具2"},},})db.Create(&Girl{Name: "三玖",Toys: []Toy{{Name: "玩具3"},{Name: "玩具4"},},})
}
它創建出來的表:
我們可以看到在toys
表中我們僅僅用owner_type
與owner_id
就完成了對boys
與gils
表的區分,避免了不必要的麻煩
補充:
可以使用標簽 polymorphicValue 來更改多態類型的值,像下面這樣:
type Boy struct {gorm.ModelName stringToys []Toy `gorm:"polymorphic:Owner;polymorphicValue:bbbb"`
}type Girl struct {gorm.ModelName stringToys []Toy `gorm:"polymorphic:Owner;polymorphicValue:gggg"`
}type Toy struct {gorm.ModelName stringOwnerID uintOwnerType string
}
創建出來的表:
關聯標簽
前言
關聯標簽這里我們主要介紹四個:
foreignKey
:references
:joinForeignKey
joinReferences
foreignKey與references
這里我們用一對多的多表關系來解釋
type Boy struct {gorm.ModelName stringToys []Toy `gorm:"polymorphic:Owner;foreign:Name;references:BoyName"`
}type Toy struct {gorm.ModelToyName stringBoyName stringOwnerID uintOwnerType string
}
如上面所示:
foreignKey
:用來指定連接表的外鍵。
references
:用來指定引用表的列名與連接表的外鍵映射。
joinForeignKey與joinReferences
joinForeignKey
:指定Many to Many產生的連接表中關聯外鍵映射字段的名稱。
joinReferences
:指定Many to Many產生的連接表中關聯外鍵字段的名稱。
這里的演示我們用多對多的多表關系來演示:
package mainimport ("gorm.io/gorm""gorm/ConnectDB"
)type Girl struct {gorm.ModelToyName stringName stringToys []Toy `gorm:"many2many:girls_toys;foreign:ToyName;joinForeignKey:a;joinReferences:b"`
}type Toy struct {gorm.ModelToyName string
}func main() {db, err := ConnectDB.Connect()if err != nil {panic(err)}err = db.AutoMigrate(&Girl{}, &Toy{})if err != nil {panic(err)}db.Create(&Girl{Name: "三玖",Toys: []Toy{{ToyName: "玩具3"},{ToyName: "玩具4"},},})
}
它創建出來的連接表是這樣的:
用通俗的方式來說,其實它們的作用就是決定了連接表的列名。
自定義數據類型
前言
在GORM
中允許我們去使用自定義的數據類型,但是我們必須要實現Scanner
與Value
接口,以便讓GORM
知道如何接收并保存該類型到數據庫中。
自定義結構體
package mainimport ("database/sql/driver""encoding/json""errors""fmt""gorm/ConnectDB"
)type User struct {ID uintInfo UserInfo
}type UserInfo struct {Name stringAge int
}func (u *UserInfo) Scan(value interface{}) error {bytes, ok := value.([]byte)if !ok {return errors.New(fmt.Sprintf("Scan failed: %v", value))}info := UserInfo{}err := json.Unmarshal(bytes, &info)*u = inforeturn err
}func (u UserInfo) Value() (driver.Value, error) {return json.Marshal(u)
}func main() {db, err := ConnectDB.Connect()if err != nil {fmt.Println("數據庫連接失敗,err:", err)return}err = db.AutoMigrate(&User{})if err != nil {fmt.Println("表創建失敗,err:", err)return}user := User{Info: UserInfo{Name: "張三",Age: 18,},}db.Create(&user)db.First(&user)fmt.Println(user)
}
自定義數組
func (a *Args) Scan(value interface{}) error {str, ok := value.([]byte)if !ok {return errors.New(fmt.Sprintf("Scan failed: %v", value))}*a = strings.Split(string(str), ",")return nil
}func (a Args) Value() (driver.Value, error) {if len(a) > 0 {var str stringstr = a[0]for i := 1; i < len(a); i++ {str += "," + a[i]}return str, nil}return "", nil
}
事務
前言
事務就是用戶定義的一系列數據庫操作,這些操作可以視為一個完成的邏輯處理工作單元,要么全部執行,要么全部不執行,是不可分割的工作單元。很形象的一個例子,張三給李四轉賬100元,在程序里面,張三的余額就要-100,李四的余額就要+100 整個事件是一個整體,哪一步錯了,整個事件都是失敗的
gorm事務默認是開啟的。為了確保數據一致性,GORM 會在事務里執行寫入操作(創建、更新、刪除)。如果沒有這方面的要求,我們可以在初始化時禁用它,這將獲得大約 30%+ 性能提升。但是一般不推薦禁用。
相關表結構
我們這里相關表結構
type User struct {ID uint `json:"id"`Name string `json:"name"`Money int `json:"money"`
}
事務的使用
現在有一個場景:,張三給李四轉賬100元,在程序里面,張三的余額就要-100,李四的余額就要+100
如果不使用事務,是這樣的:
var zhangsan, lisi User
DB.Take(&zhangsan, "name = ?", "張三")
DB.Take(&lisi, "name = ?", "李四")// 先給張三-100
zhangsan.Money -= 100
DB.Model(&zhangsan).Update("money", zhangsan.Money)// 再給李四+100
lisi.Money += 100
DB.Model(&lisi).Update("money", lisi.Money)
在失敗的情況下,要么張三白白損失了100,要么李四憑空拿到100元這顯然是不合邏輯的,并且不合法的,而這就需要我們來使用事務了,事務一共分為兩種:
自動事務
手動事務
我們分別來看一下它們的寫法:
- 自動事務:
var zhangsan, lisi User
DB.Take(&zhangsan, "name = ?", "張三")
DB.Take(&lisi, "name = ?", "李四")
// 張三給李四轉賬100元
DB.Transaction(func(tx *gorm.DB) error {// 先給張三-100zhangsan.Money -= 100err := tx.Model(&zhangsan).Update("money", zhangsan.Money).Errorif err != nil {fmt.Println(err)return err}// 再給李四+100lisi.Money += 100err = tx.Model(&lisi).Update("money", lisi.Money).Errorif err != nil {fmt.Println(err)return err}// 提交事務return nil
})
-
手動事務:
- 執行流程:
//開始事務 tx := db.Begin()// 在事務中執行一些 db 操作(從這里開始,您應該使用 'tx' 而不是 'db') tx.Create(...)// ...// 遇到錯誤時回滾事務 tx.Rollback()// 否則,提交事務 tx.Commit()
var zhangsan, lisi User
DB.Take(&zhangsan, "name = ?", "張三")
DB.Take(&lisi, "name = ?", "李四")// 張三給李四轉賬100元
tx := DB.Begin()// 先給張三-100
zhangsan.Money -= 100
err := tx.Model(&zhangsan).Update("money", zhangsan.Money).Error
if err != nil {tx.Rollback()
}// 再給李四+100
lisi.Money += 100
err = tx.Model(&lisi).Update("money", lisi.Money).Error
if err != nil {tx.Rollback()
}
// 提交事務
tx.Commit()
結語
至此,GORM
的學習就告一段落了,大家下篇文章見,拜拜!