一对多是业务中最常见的关联关系。一个用户有多篇文章、一个分类下有多个商品、一个部门有多个员工。
用户有多篇文章:
type User struct {
ID uint
Name string
Articles []Article
}
type Article struct {
ID uint
UserID uint
Title string
}
Article 通过 UserID 外键关联到 User。
默认外键是 模型名 + ID,即 UserID。
自定义外键:
type User struct {
ID uint
Name string
Articles []Article `gorm:"foreignKey:AuthorID"`
}
type Article struct {
ID uint
AuthorID uint
Title string
}
默认引用主键。自定义引用键:
type User struct {
ID uint
Username string `gorm:"unique"`
Articles []Article `gorm:"foreignKey:AuthorName;references:Username"`
}
type Article struct {
ID uint
AuthorName string
Title string
}
创建用户时同时创建文章:
user := User{
Name: "张三",
Articles: []Article{
{Title: "文章1"},
{Title: "文章2"},
},
}
db.Create(&user)
三张表都会创建,外键自动设置。
var user User
db.Preload("Articles").First(&user, 1)
fmt.Println(len(user.Articles))
条件预加载:
db.Preload("Articles", "status = ?", "published").First(&user, 1)
只加载已发布的文章。
给用户添加新文章:
var user User
db.First(&user, 1)
article := Article{Title: "新文章"}
db.Model(&user).Association("Articles").Append(&article)
删除用户的某篇文章关联:
db.Model(&user).Association("Articles").Delete(&article)
注意:这只是删除关联(外键置空),不删除文章记录。
要删除记录:
db.Delete(&article)
清空用户的所有文章关联:
db.Model(&user).Association("Articles").Clear()
count := db.Model(&user).Association("Articles").Count()
反向的多对一,文章属于用户:
type Article struct {
ID uint
UserID uint
User User
Title string
}
查询文章时获取用户:
var article Article
db.Preload("User").First(&article, 1)
fmt.Println(article.User.Name)
同时定义 Has Many 和 Belongs To:
type User struct {
ID uint
Name string
Articles []Article
}
type Article struct {
ID uint
UserID uint
User User
Title string
}
这样可以从用户查文章,也可以从文章查用户。
分类与商品
type Category struct {
ID uint
Name string
Products []Product
}
type Product struct {
ID uint
CategoryID uint
Category Category
Name string
Price float64
}
var category Category
db.Preload("Products").First(&category, 1)
部门与员工
type Department struct {
ID uint
Name string
Employees []Employee
}
type Employee struct {
ID uint
DepartmentID uint
Department Department
Name string
}
订单与订单项
type Order struct {
ID uint
Items []OrderItem
}
type OrderItem struct {
ID uint
OrderID uint
ProductID uint
Quantity int
Price float64
}
var order Order
db.Preload("Items").First(&order, 1)
删除用户时同时删除文章:
type User struct {
ID uint
Articles []Article `gorm:"constraint:OnDelete:CASCADE;"`
}
或者手动删除:
db.Select("Articles").Delete(&user)
数据库层面设置约束:
type Article struct {
ID uint
UserID uint `gorm:"constraint:OnDelete:CASCADE,OnUpdate:CASCADE;"`
Title string
}
约束选项:
OnDelete:CASCADE - 删除用户时删除文章OnDelete:SET NULL - 删除用户时文章外键置空OnDelete:RESTRICT - 有文章时禁止删除用户OnUpdate:CASCADE - 更新用户ID时同步更新文章外键关联为空
忘记预加载:
var user User
db.First(&user, 1)
fmt.Println(user.Articles)
Articles 是空切片。必须预加载:
db.Preload("Articles").First(&user, 1)
N+1 问题
循环中查询关联:
var users []User
db.Find(&users)
for _, user := range users {
db.Model(&user).Association("Articles").Count()
}
应该预加载:
db.Preload("Articles").Find(&users)
大量关联数据
用户有上万篇文章,预加载会占用大量内存。解决方案:
一对多是最常用的关联关系。理解 Has Many 和 Belongs To 的双向关系,注意预加载避免 N+1 问题。