Gorm 关联模型
社区文档的gorm关联模型,缺少实操数据的情况下,会让新同学不明觉厉,本文用实例说话,对新手更友好。
Struct vs Model
结构体本身只是描述数据的内存布局,可以视作经典类语言中的属性或状态部分。
Gorm中的Model,不仅仅是结构体本身,它是一组收集了对该数据集进行处理的操作集,换而言之是状态+算法。
在使用Gorm的时候 ,我们只是完成对结构体数据声明,用gorm特制标签(内部通过Go类型与值反射API来间接创建原始建表语句)实现迁移,或者是结构体部分数据填充当作查询条件,进行 curd
操作。当然,都得向Gorm提供结构体指针变量,以便gorm内部可寻值,将值数据填充到声明的结构体变量,从而无需使用传统显性方法返回形式获取数据结果集。
需要注意的是Go语言是面向接口编程,换而言之,其基于组合的继承,一个结构体A内嵌另外一个结构体B,你并不能断言A的实例是B类型,这与传统的面向对象编程A继承B,A的实例既是A类型也是B类型有所不同。但的若内嵌接口类型B,那可断言A的实例变量即是A结构体类型,也是B接口类型,实现接口多态。这一点,有使用Iris经验的同学,应该深有体会(iris.context本身是接口类型,gin.Context,gorm.Model是结构体)。
Association
使用automigrate 进行迁移,关联关系会被忽略,需要在其执行之后手动
AddForeignKey
添加外键
结构体中声明的关联关系字段需与外键指向的字段保持一致,但关联关系字段不会加入数据库关系主表结构。
以places表关联towns表示例,places结构体持有的Town类型字段不会被视作Place表中的字段,它只是显性的定义了一个关联关系字段,需要Association方法使用查询构建器指定待填充的属性字段,然后用Find方法填充该类型字段,以方便place数据嵌套显示,主要代码执行呈现如下
关联是用来定义结构体(或模型)间是如何进行交互
One2one
结构体
Place.go
package model
import ()
type Place struct {
ID int `gorm:primary_key`
Name string
Town Town
TownId int `gorm:"ForeignKey:id"` //this foreignKey tag didn't works
}
Town.go
package model
import ()
type Town struct {
ID int `gorm:"primary_key"`
Name string
}
一对一
package main
import (
_ "database/sql"
"fmt"
"log"
"pardon/relationship/one2one/model"
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
)
func main() {
Db, err := gorm.Open("mysql", "root:secret@tcp(127.0.0.1:3306)/godb?charset=utf8&parseTime=True")
defer Db.Close()
if err != nil {
log.Fatal(err)
}
Db.DropTableIfExists(&model.Place{}, &model.Town{})
Db.AutoMigrate(&model.Place{}, &model.Town{})
// AutoMigrate 会忽略外键,需手动添加
// 参数分别为模型外键,关联表主键,删除级联,修改级联
Db.Model(&model.Place{}).AddForeignKey("town_id", "towns(id)", "CASCADE", "CASCADE")
t1 := model.Town{
Name: "Pune",
}
t2 := model.Town{
Name: "Mumbai",
}
t3 := model.Town{
Name: "Hyderabad",
}
p1 := model.Place{
Name: "Katraj",
Town: t1,
}
p2 := model.Place{
Name: "Thane",
Town: t2,
}
p3 := model.Place{
Name: "Secundarabad",
Town: t3,
}
// 添加关联数据
Db.Debug().Save(&p1)
Db.Debug().Save(&p2)
Db.Debug().Save(&p3)
// 触发数据库级联删除
Db.Debug().Where("name=?", "Hyderabad").Delete(&model.Town{})
// 属性修改
Db.Debug().Model(&model.Place{}).Where("id=?", 1).Update("name", "Shivaji Nagar")
// 查询
places := model.Place{}
towns := model.Town{}
fmt.Println("Before Association", places)
Db.Debug().Where("name=?", "Shivaji Nagar").Find(&places)
fmt.Println("After Assocciation", places)
err = Db.Debug().Model(&places).Association("town").Find(&places.Town).Error
fmt.Println("After Association", towns, places)
fmt.Println("After Association", towns, places, err)
}
One2many
与 Asscociation
惰性加载没同,preload
是主动预加载,它会主动加载尽可能多的数据。
package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
)
type Customer struct {
CustomerID int `gorm:"primary_key"`
CustomerName string
Contacts []Contact `gorm:"ForeignKey:CustId"`
}
type Contact struct {
ContactID int `gorm:"primary_key"`
CountryCode int
MobileNo uint
CustId int
}
func main() {
db, err := gorm.Open("mysql", "root:secret@tcp(127.0.0.1:3306)/godb?charset=utf8&parseTime=True")
if err != nil {
panic(err.Error())
}
defer db.Close()
db.DropTableIfExists(&Contact{}, &Customer{})
db.AutoMigrate(&Customer{}, &Contact{})
db.Model(&Contact{}).AddForeignKey("cust_id", "customers(customer_id)", "CASCADE", "CASCADE")
Custs1 := Customer{CustomerName: "John", Contacts: []Contact{
{CountryCode: 91, MobileNo: 956112},
{CountryCode: 91, MobileNo: 997555}}}
Custs2 := Customer{CustomerName: "Martin", Contacts: []Contact{
{CountryCode: 90, MobileNo: 808988},
{CountryCode: 90, MobileNo: 909699}}}
Custs3 := Customer{CustomerName: "Raym", Contacts: []Contact{
{CountryCode: 75, MobileNo: 798088},
{CountryCode: 75, MobileNo: 965755}}}
Custs4 := Customer{CustomerName: "Stoke", Contacts: []Contact{
{CountryCode: 80, MobileNo: 805510},
{CountryCode: 80, MobileNo: 758863}}}
db.Debug().Create(&Custs1)
db.Debug().Create(&Custs2)
db.Debug().Create(&Custs3)
db.Debug().Create(&Custs4)
customers := &Customer{}
//contacts := &Contact{}
db.Debug().Where("customer_name=?", "Martin").Preload("Contacts").Find(&customers)
fmt.Println("Customers", customers)
db.Debug().Model(&Contact{}).Where("cust_id=?", 3).Update("country_code", 77)
db.Debug().Where("customer_name=?", customers.CustomerName).Delete(&customers)
fmt.Println("After Delete", customers)
}
Many2many
使用gorm的many2many 结构体标签,同样用Assocation加载关联关系,自动迁移,手动绑定生成外键
源码
package main
import (
_ "database/sql"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
)
type UserL struct {
gorm.Model
Uname string
Languages []Language `gorm:"many2many:user_languages;"`
}
type Language struct {
gorm.Model
Name string
}
type UserLanguages struct {
UserLId uint
LanguageId uint
}
func main() {
db, err := gorm.Open("mysql", "root:secret@(127.0.0.1:3306)/godb?charset=utf8&parseTime=True")
if err != nil {
log.Fatal(err)
}
defer db.Close()
db.DropTableIfExists(&UserLanguages{}, &Language{}, &UserL{})
db.AutoMigrate(&UserL{}, &Language{}, &UserLanguages{})
db.Model(UserLanguages{}).AddForeignKey("user_l_id", "user_ls(id)", "CASCADE", "CASCADE")
db.Model(UserLanguages{}).AddForeignKey("language_id", "languages(id)", "CASCADE", "CASCADE")
langs := []Language{{Name: "English"}, {Name: "French"}}
user1 := UserL{Uname: "John", Languages: langs}
user2 := UserL{Uname: "Martin", Languages: langs}
user3 := UserL{Uname: "Ray", Languages: langs}
db.Debug().Save(&user1) //save is happening
db.Debug().Save(&user2)
db.Debug().Save(&user3)
fmt.Println("After Saving Records")
fmt.Println("User1", &user1)
fmt.Println("User2", &user2)
fmt.Println("User3", &user3)
user := &UserL{}
db.Debug().Where("uname=?", "Ray").Find(&user)
err = db.Debug().Model(&user).Association("Languages").Find(&user.Languages).Error
fmt.Println("User is now comming", user, err)
fmt.Println(user, "to delete")
db.Debug().Where("uname=?", "John").Delete(&user)
db.Debug().Model(&UserL{}).Where("uname=?", "Ray").Update("uname", "Martin")
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
Association和Preload区别是啥呢?