一对一关联是最简单的关联关系。一个模型拥有另一个模型,比如用户和用户资料、文章和作者。
用户有一个资料卡:
type User struct {
ID uint
Name string
Profile Profile
}
type Profile struct {
ID uint
UserID uint
Bio string
Avatar string
}
Profile 通过 UserID 外键关联到 User。GORM 自动识别这种关系。
默认外键名是 模型名 + ID,即 UserID。
自定义外键:
type User struct {
ID uint
Name string
Profile Profile `gorm:"foreignKey:UserRef"`
}
type Profile struct {
ID uint
UserRef uint
Bio string
}
默认引用主键。自定义引用键:
type User struct {
ID uint
UUID string `gorm:"unique"`
Profile Profile `gorm:"foreignKey:UserUUID;references:UUID"`
}
type Profile struct {
ID uint
UserUUID string
Bio string
}
创建用户时同时创建资料:
user := User{
Name: "张三",
Profile: Profile{
Bio: "这是个人简介",
Avatar: "avatar.jpg",
},
}
db.Create(&user)
两条记录都会创建,外键自动设置。
var user User
db.Preload("Profile").First(&user, 1)
fmt.Println(user.Profile.Bio)
用 Joins 预加载:
db.Joins("Profile").First(&user, 1)
var user User
db.First(&user, 1)
user.Profile.Bio = "更新后的简介"
db.Save(&user)
或者直接更新关联:
db.Model(&user).Association("Profile").Update(&Profile{Bio: "新简介"})
删除用户时,关联的资料怎么处理?
默认不处理,资料保留。可以通过外键约束设置级联:
type User struct {
ID uint
Name string
Profile Profile `gorm:"constraint:OnDelete:CASCADE;"`
}
删除用户时自动删除资料。
反向的一对一,资料属于用户:
type Profile struct {
ID uint
UserID uint
User User
Bio string
}
Profile 通过 UserID 和 User 关联,表示资料属于某个用户。
自定义外键:
type Profile struct {
ID uint
User User `gorm:"foreignKey:UserRef"`
UserRef uint
Bio string
}
语义上的区别:
数据库层面,两种方式的外键都在"多"的一方。一对一关系里,外键在哪张表,那张表就是 Belongs To。
用户与身份证
type User struct {
ID uint
Name string
IDCard IDCard
}
type IDCard struct {
ID uint
UserID uint
Number string
IssueDate time.Time
}
订单与支付
type Order struct {
ID uint
Amount float64
Payment Payment
}
type Payment struct {
ID uint
OrderID uint
Method string
PaidAt time.Time
}
文章与封面
type Article struct {
ID uint
Title string
Cover Image
}
type Image struct {
ID uint
ArticleID uint
URL string
}
外键为空
查询时关联为空:
var user User
db.First(&user, 1)
fmt.Println(user.Profile)
没有预加载,Profile 是空的。必须预加载:
db.Preload("Profile").First(&user, 1)
关联不存在
如果用户没有资料,Profile 是零值结构体。检查:
if user.Profile.ID == 0 {
// 资料不存在
}
循环引用
type User struct {
ID uint
Profile Profile
}
type Profile struct {
ID uint
UserID uint
User User
}
会导致无限递归。解决方案:一方不存反向引用,或者用指针。
一对一关联简单常用。理解 Has One 和 Belongs To 的语义区别,注意外键和引用键的配置。