前言
本来是想着写多表关系的,不过写了一半发现重复的部分太多了,想了想与其做一些重复性工作,不如把一些当时觉得抽象的东西记录一下,就当用一篇杂记完成专栏的最后一篇文章吧。
预加载
简单示例
预加载主要用于在多表关系中加载关联表的信息,在讲解预加载的类型之前我们先来看一个预加载的示例:
- 相关表结构
type User struct { gorm.Model Name string Languages []Language `gorm:"many2many:user_languages;"` } type Language struct { gorm.Model Name string Users []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 *string StudentID uint `gorm:"size:8"` }
- 示例:
var student Student db.Joins("Info").Take(&student) db.Joins("Info", db.Where("Info.Age = ?", 18)) //带条件的Joins fmt.Println(student)
条件预加载
var student Student db.Where("age > ?", 18).Preload("Info").First(&student) //方式一 db.Preload("Info", "age > ?", 18).Find(&student) //方式二 fmt.Println(student)
自定义预加载
var student Student db.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 main import ( "gorm.io/gorm" "gorm/ConnectDB" ) type Boy struct { gorm.Model Name string Toys []Toy `gorm:"polymorphic:Owner"` } type Girl struct { gorm.Model Name string Toys []Toy `gorm:"polymorphic:Owner"` } type Toy struct { gorm.Model Name string OwnerID uint OwnerType 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.Model Name string Toys []Toy `gorm:"polymorphic:Owner;polymorphicValue:bbbb"` } type Girl struct { gorm.Model Name string Toys []Toy `gorm:"polymorphic:Owner;polymorphicValue:gggg"` } type Toy struct { gorm.Model Name string OwnerID uint OwnerType string }
创建出来的表:
关联标签
前言
关联标签这里我们主要介绍四个:
foreignKey
:references
:joinForeignKey
joinReferences
foreignKey与references
这里我们用一对多的多表关系来解释
type Boy struct { gorm.Model Name string Toys []Toy `gorm:"polymorphic:Owner;foreign:Name;references:BoyName"` } type Toy struct { gorm.Model ToyName string BoyName string OwnerID uint OwnerType string }
如上面所示:
foreignKey
:用来指定连接表的外键。
references
:用来指定引用表的列名与连接表的外键映射。
joinForeignKey与joinReferences
joinForeignKey
:指定Many to Many产生的连接表中关联外键映射字段的名称。
joinReferences
:指定Many to Many产生的连接表中关联外键字段的名称。
这里的演示我们用多对多的多表关系来演示:
package main import ( "gorm.io/gorm" "gorm/ConnectDB" ) type Girl struct { gorm.Model ToyName string Name string Toys []Toy `gorm:"many2many:girls_toys;foreign:ToyName;joinForeignKey:a;joinReferences:b"` } type Toy struct { gorm.Model ToyName 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 main import ( "database/sql/driver" "encoding/json" "errors" "fmt" "gorm/ConnectDB" ) type User struct { ID uint Info UserInfo } type UserInfo struct { Name string Age 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 = info return 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 string str = 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 { // 先给张三-100 zhangsan.Money -= 100 err := tx.Model(&zhangsan).Update("money", zhangsan.Money).Error if err != nil { fmt.Println(err) return err } // 再给李四+100 lisi.Money += 100 err = tx.Model(&lisi).Update("money", lisi.Money).Error if 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
的学习就告一段落了,大家下篇文章见,拜拜!