关联
Belongs To
属于
belongs to 关联建立一个和另一个模型的一对一连接,使得模型声明每个实例都「属于」另一个模型的一个实例 。
例如,如果你的应用包含了用户和用户资料, 并且每一个用户资料只分配给一个用户
代码语言:go复制type User struct {
gorm.Model
Name string
}
// `Profile` 属于 `User`, `UserID` 是外键
type Profile struct {
gorm.Model
UserID int
User User
Name string
}
外键
为了定义从属关系, 外键是必须存在的, 默认的外键使用所有者类型名称加上其主键。
像上面的例子,为了声明一个模型属于 User,它的外键应该为 UserID。
GORM 提供了一个定制外键的方法,例如:
代码语言:go复制type User struct {
gorm.Model
Name string
}
type Profile struct {
gorm.Model
Name string
User User `gorm:"foreignkey:UserRefer"` // 使用 UserRefer 作为外键
UserRefer string
}
关联外键
对于从属关系, GORM 通常使用所有者的主键作为外键值,在上面的例子中,就是 User 的 ID。
当你分配一个资料给一个用户, GORM 将保存用户表的 ID 值 到 用户资料表的 UserID 字段里。
你可以通过改变标签 association_foreignkey 来改变它, 例如:
代码语言:go复制type User struct {
gorm.Model
Refer int
Name string
}
type Profile struct {
gorm.Model
Name string
User User `gorm:"association_foreignkey:Refer"` // use Refer 作为关联外键
UserRefer string
}
使用属于
你能找到 belongs to 和 Related 的关联
代码语言:go复制db.Model(&user).Related(&profile)
//// SELECT * FROM profiles WHERE user_id = 111; // 111 is user's ID
Has One
has one 关联也是与另一个模型建立一对一的连接,但语义(和结果)有些不同。 此关联表示模型的每个实例包含或拥有另一个模型的一个实例。
例如,如果你的应用程序包含用户和信用卡,并且每个用户只能有一张信用卡。
代码语言:go复制// 用户有一个信用卡,CredtCardID 外键
type User struct {
gorm.Model
CreditCard CreditCard
}
type CreditCard struct {
gorm.Model
Number string
UserID uint
}
外键
对于一对一关系,一个外键字段也必须存在,所有者将保存主键到模型关联的字段里。
这个字段的名字通常由 belongs to model 的类型加上它的 primary key 产生的,就上面的例子而言,它就是 CreditCardID
当你给用户一个信用卡, 它将保存一个信用卡的 ID 到 CreditCardID 字段中。
如果你想使用另一个字段来保存这个关系,你可以通过使用标签 foreignkey 来改变它, 例如:
代码语言:go复制type User struct {
gorm.Model
CreditCard CreditCard `gorm:"foreignkey:CardRefer"`
}
type CreditCard struct {
gorm.Model
Number string
UserName string
}
关联外键
通常,所有者会保存 belogns to model 的主键到外键,你可以改为保存其他字段, 就像下面的例子使用 Number 。
代码语言:go复制type User struct {
gorm.Model
CreditCard CreditCard `gorm:"association_foreignkey:Number"`
}
type CreditCard struct {
gorm.Model
Number string
UID string
}
多态关联
支持多态的一对多和一对一关联。
代码语言:go复制 type Cat struct {
ID int
Name string
Toy Toy `gorm:"polymorphic:Owner;"`
}
type Dog struct {
ID int
Name string
Toy Toy `gorm:"polymorphic:Owner;"`
}
type Toy struct {
ID int
Name string
OwnerID int
OwnerType string
}
注意:多态属于和多对多是明确的不支持并将会抛出错误。
使用一对一
你可以通过 Related 找到 has one 关联。
代码语言:go复制var card CreditCard
db.Model(&user).Related(&card, "CreditCard")
//// SELECT * FROM credit_cards WHERE user_id = 123; // 123 是用户表的主键
// CreditCard 是用户表的字段名,这意味着获取用户的信用卡关系并写入变量 card。
// 像上面的例子,如果字段名和变量类型名一样,它就可以省略, 像:
db.Model(&user).Related(&card)
Has Many
一对多
has many 关联就是创建和另一个模型的一对多关系, 不像 has one,所有者可以拥有0个或多个模型实例。
例如,如果你的应用包含用户和信用卡, 并且每一个用户都拥有多张信用卡。
代码语言:go复制// 用户有多张信用卡,UserID 是外键
type User struct {
gorm.Model
CreditCards []CreditCard
}
type CreditCard struct {
gorm.Model
Number string
UserID uint
}
外键
为了定义一对多关系, 外键是必须存在的,默认外键的名字是所有者类型的名字加上它的主键。
就像上面的例子,为了定义一个属于User 的模型,外键就应该为 UserID。
使用其他的字段名作为外键, 你可以通过 foreignkey 来定制它, 例如:
代码语言:go复制type User struct {
gorm.Model
CreditCards []CreditCard `gorm:"foreignkey:UserRefer"`
}
type CreditCard struct {
gorm.Model
Number string
UserRefer uint
}
外键关联
GORM 通常使用所有者的主键作为外键的值, 在上面的例子中,它就是 User 的 ID。
当你分配信用卡给一个用户, GORM 将保存用户 ID 到信用卡表的 UserID 字段中。
你能通过 association_foreignkey 来改变它, 例如:
代码语言:go复制type User struct {
gorm.Model
MemberNumber string
CreditCards []CreditCard `gorm:"foreignkey:UserMemberNumber;association_foreignkey:MemberNumber"`
}
type CreditCard struct {
gorm.Model
Number string
UserMemberNumber string
}
多态关联
支持多态的一对多和一对一关联。
代码语言:go复制 type Cat struct {
ID int
Name string
Toy []Toy `gorm:"polymorphic:Owner;"`
}
type Dog struct {
ID int
Name string
Toy []Toy `gorm:"polymorphic:Owner;"`
}
type Toy struct {
ID int
Name string
OwnerID int
OwnerType string
}
注意:多态属于和多对多是明确不支持并会抛出错误的。
使用一对多
你可以通过Related 找到 has many 关联关系。
代码语言:go复制db.Model(&user).Related(&emails)
//// SELECT * FROM emails WHERE user_id = 111; // 111 是用户表的主键
Many To Many
多对多
多对多为两个模型增加了一个中间表。
例如,如果你的应用包含用户和语言, 一个用户会说多种语言,并且很多用户会说一种特定的语言。
代码语言:go复制// 用户拥有并属于多种语言, 使用 `user_languages` 作为中间表
type User struct {
gorm.Model
Languages []Language `gorm:"many2many:user_languages;"`
}
type Language struct {
gorm.Model
Name string
}
反向关联
代码语言:go复制// 用户拥有并且属于多种语言,使用 `user_languages` 作为中间表
type User struct {
gorm.Model
Languages []*Language `gorm:"many2many:user_languages;"`
}
type Language struct {
gorm.Model
Name string
Users []*User `gorm:"many2many:user_languages;"`
}
db.Model(&language).Related(&users)
//// SELECT * FROM "users" INNER JOIN "user_languages" ON "user_languages"."user_id" = "users"."id" WHERE ("user_languages"."language_id" IN ('111'))
外键
代码语言:go复制type CustomizePerson struct {
IdPerson string `gorm:"primary_key:true"`
Accounts []CustomizeAccount `gorm:"many2many:PersonAccount;association_foreignkey:idAccount;foreignkey:idPerson"`
}
type CustomizeAccount struct {
IdAccount string `gorm:"primary_key:true"`
Name string
}
外键会为两个结构体创建一个多对多的关系,并且这个关系将通过外键customize_person_id_person 和 customize_account_id_account 保存到中间表 PersonAccount。
中间表外键
如果你想改变中间表的外键,你可以用标签 association_jointable_foreignkey, jointable_foreignkey
代码语言:go复制type CustomizePerson struct {
IdPerson string `gorm:"primary_key:true"`
Accounts []CustomizeAccount `gorm:"many2many:PersonAccount;foreignkey:idPerson;association_foreignkey:idAccount;association_jointable_foreignkey:account_id;jointable_foreignkey:person_id;"`
}
type CustomizeAccount struct {
IdAccount string `gorm:"primary_key:true"`
Name string
}
自引用
为了定义一个自引用的多对多关系,你不得不改变中间表的关联外键。
和来源表外键不同的是它是通过结构体的名字和主键生成的,例如:
代码语言:go复制type User struct {
gorm.Model
Friends []*User `gorm:"many2many:friendships;association_jointable_foreignkey:friend_id"`
}
GORM 将创建一个带外键 user_id 和 friend_id 的中间表, 并且使用它去保存用户表的自引用关系。
然后你可以像普通关系一样操作它, 例如:
代码语言:go复制DB.Preload("Friends").First(&user, "id = ?", 1)
DB.Model(&user).Association("Friends").Append(&User{Name: "friend1"}, &User{Name: "friend2"})
DB.Model(&user).Association("Friends").Delete(&User{Name: "friend2"})
DB.Model(&user).Association("Friends").Replace(&User{Name: "new friend"})
DB.Model(&user).Association("Friends").Clear()
DB.Model(&user).Association("Friends").Count()
使用多对多
代码语言:go复制db.Model(&user).Related(&languages, "Languages")
//// SELECT * FROM "languages" INNER JOIN "user_languages" ON "user_languages"."language_id" = "languages"."id" WHERE "user_languages"."user_id" = 111
// 当查询用户时预加载 Language
db.Preload("Languages").First(&user)
关联
自动创建/更新
GORM 将在创建或保存一条记录的时候自动保存关联和它的引用,如果关联有一个主键, GORM 将调用 Update 来更新它, 不然,它将会被创建。
代码语言:go复制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@example.com"},
},
Languages: []Language{
{Name: "ZH"},
{Name: "EN"},
},
}
db.Create(&user)
//// BEGIN TRANSACTION;
//// INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1");
//// INSERT INTO "addresses" (address1) VALUES ("Shipping Address - Address 1");
//// 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");
//// INSERT INTO "emails" (user_id,email) VALUES (111, "jinzhu-2@example.com");
//// INSERT INTO "languages" ("name") VALUES ('ZH');
//// INSERT INTO user_languages ("user_id","language_id") VALUES (111, 1);
//// INSERT INTO "languages" ("name") VALUES ('EN');
//// INSERT INTO user_languages ("user_id","language_id") VALUES (111, 2);
//// COMMIT;
db.Save(&user)
关闭自动更新
如果你的关联记录已经存在在数据库中, 你可能会不想去更新它。
你可以设置 gorm:association_autoupdate 为 false
代码语言:go复制// 不更新有主键的关联,但会更新引用
db.Set("gorm:association_autoupdate", false).Create(&user)
db.Set("gorm:association_autoupdate", false).Save(&user)
或者使用 GORM 的标签, gorm:"association_autoupdate:false"
type User struct {
gorm.Model
Name string
CompanyID uint
// 不更新有主键的关联,但会更新引用
Company Company `gorm:"association_autoupdate:false"`
}
关闭自动创建
即使你禁用了 AutoUpdating, 仍然会创建没有主键的关联,并保存它的引用。
你可以通过把 gorm:association_autocreate 设置为 false 来禁用这个行为。
代码语言:go复制// 不创建没有主键的关联,不保存它的引用。
db.Set("gorm:association_autocreate", false).Create(&user)
db.Set("gorm:association_autocreate", false).Save(&user)
或者使用 GORM 标签, gorm:"association_autocreate:false"
type User struct {
gorm.Model
Name string
// 不创建没有主键的关联,不保存它的引用。
Company1 Company `gorm:"association_autocreate:false"`
}
关闭自动创建/更新
禁用 AutoCreate 和 AutoUpdate,你可以一起使用它们两个的设置。
代码语言:go复制db.Set("gorm:association_autoupdate", false).Set("gorm:association_autocreate", false).Create(&user)
type User struct {
gorm.Model
Name string
Company Company `gorm:"association_autoupdate:false;association_autocreate:false"`
}
或者使用 gorm:save_associations
代码语言:go复制db.Set("gorm:save_associations", false).Create(&user)
db.Set("gorm:save_associations", false).Save(&user)
type User struct {
gorm.Model
Name string
Company Company `gorm:"association_autoupdate:false"`
}
关闭保存引用
如果你不想当更新或保存数据的时候保存关联的引用, 你可以使用下面的技巧
代码语言:go复制db.Set("gorm:association_save_reference", false).Save(&user)
db.Set("gorm:association_save_reference", false).Create(&user)
或者使用标签
type User struct {
gorm.Model
Name string
CompanyID uint
Company Company `gorm:"association_save_reference:false"`
}
关联模式
关联模式包含一些可以轻松处理与关系相关的事情的辅助方法。
代码语言:go复制// 开启关联模式
var user User
db.Model(&user).Association("Languages")
// `user` 是源表,必须包含主键
// `Languages` 是源表关系字段名称。
// 只有上面两个条件都能匹配,关联模式才会生效, 检查是否正常:
// db.Model(&user).Association("Languages").Error
查找关联
查找匹配的关联
代码语言:go复制db.Model(&user).Association("Languages").Find(&languages)
增加关联
为 many to many, has many 新增关联, 为 has one, belongs to 替换当前关联
代码语言:go复制db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN})
db.Model(&user).Association("Languages").Append(Language{Name: "DE"})
替换关联
用一个新的关联替换当前关联
代码语言:go复制db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN})
db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN)
删除关联
删除源和参数对象之间的关系, 只会删除引用,不会删除他们在数据库中的对象。
代码语言:go复制db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN})
db.Model(&user).Association("Languages").Delete(languageZH, languageEN)
清理关联
删除源和当前关联之间的引用,不会删除他们的关联
代码语言:go复制db.Model(&user).Association("Languages").Clear()
统计关联
返回当前关联的统计数
代码语言:go复制db.Model(&user).Association("Languages").Count()
预加载
预加载
代码语言:go复制db.Preload("Orders").Find(&users)
//// SELECT * FROM users;
//// SELECT * FROM orders WHERE user_id IN (1,2,3,4);
db.Preload("Orders", "state NOT IN (?)", "cancelled").Find(&users)
//// SELECT * FROM users;
//// SELECT * FROM orders WHERE user_id IN (1,2,3,4) AND state NOT IN ('cancelled');
db.Where("state = ?", "active").Preload("Orders", "state NOT IN (?)", "cancelled").Find(&users)
//// SELECT * FROM users WHERE state = 'active';
//// SELECT * FROM orders WHERE user_id IN (1,2) AND state NOT IN ('cancelled');
db.Preload("Orders").Preload("Profile").Preload("Role").Find(&users)
//// SELECT * FROM users;
//// SELECT * FROM orders WHERE user_id IN (1,2,3,4); // has many
//// SELECT * FROM profiles WHERE user_id IN (1,2,3,4); // has one
//// SELECT * FROM roles WHERE id IN (4,5,6); // belongs to
自动预加载
始终自动预加载的关联
代码语言:go复制type User struct {
gorm.Model
Name string
CompanyID uint
Company Company `gorm:"PRELOAD:false"` //没有预加载
Role Role // 已经预加载
}
db.Set("gorm:auto_preload", true).Find(&users)
嵌套预加载
代码语言:go复制db.Preload("Orders.OrderItems").Find(&users)
db.Preload("Orders", "state = ?", "paid").Preload("Orders.OrderItems").Find(&users)
自定义预加载 SQL
您可以通过传入func(db gorm.DB) gorm.DB来自定义预加载SQL,例如:
代码语言:go复制db.Preload("Orders", func(db *gorm.DB) *gorm.DB {
return db.Order("orders.amount DESC")
}).Find(&users)
//// SELECT * FROM users;
//// SELECT * FROM orders WHERE user_id IN (1,2,3,4) order by orders.amount DESC;