TORM-数据关系

73 阅读6分钟

e9e1cd7a1b744443be876f5ef131c10d~tplv-73owjymdk6-jj-mark-v1_0_0_0_0_5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5oeS5b6X5pu05paw_q75.webp

TORM 提供了完整的关联关系支持,包括 HasOne、HasMany、BelongsTo、ManyToMany 等关系类型,并支持关联预加载来解决 N+1 查询问题。

📋 目录

🚀 快速开始

基础关联定义

// User 用户模型
type User struct {
    model.BaseModel
    ID        int64     `json:"id" db:"id"`
    Name      string    `json:"name" db:"name"`
    Email     string    `json:"email" db:"email"`
    CreatedAt time.Time `json:"created_at" db:"created_at"`
}

// Post 文章模型
type Post struct {
    model.BaseModel
    ID        int64     `json:"id" db:"id"`
    UserID    int64     `json:"user_id" db:"user_id"`
    Title     string    `json:"title" db:"title"`
    Content   string    `json:"content" db:"content"`
    CreatedAt time.Time `json:"created_at" db:"created_at"`
}

// Profile 用户档案模型
type Profile struct {
    model.BaseModel
    ID       int64  `json:"id" db:"id"`
    UserID   int64  `json:"user_id" db:"user_id"`
    Avatar   string `json:"avatar" db:"avatar"`
    Bio      string `json:"bio" db:"bio"`
}

🔗 关系类型

1. HasOne (一对一)

用户有一个档案:

// 在 User 模型中定义
func (u *User) Profile() *model.HasOne {
    return u.HasOne(&Profile{}, "user_id", "id")
}

// 使用关联
user := NewUser()
err := user.Find(1)
profile, err := user.Profile().First()

2. HasMany (一对多)

用户有多篇文章:

// 在 User 模型中定义
func (u *User) Posts() *model.HasMany {
    return u.HasMany(&Post{}, "user_id", "id")
}

// 使用关联
user := NewUser()
err := user.Find(1)
posts, err := user.Posts().Get()

// 带条件的关联查询
publishedPosts, err := user.Posts().
    Where("status", "=", "published").
    OrderBy("created_at", "desc").
    Get()

3. BelongsTo (反向一对一/一对多)

文章属于用户:

// 在 Post 模型中定义
func (p *Post) User() *model.BelongsTo {
    return p.BelongsTo(&User{}, "user_id", "id")
}

// 使用关联
post := NewPost()
err := post.Find(1)
user, err := post.User().First()

4. ManyToMany (多对多)

用户和角色的多对多关系:

// Role 角色模型
type Role struct {
    model.BaseModel
    ID   int64  `json:"id" db:"id"`
    Name string `json:"name" db:"name"`
}

// 在 User 模型中定义
func (u *User) Roles() *model.ManyToMany {
    return u.ManyToMany(&Role{}, "user_roles", "user_id", "role_id")
}

// 在 Role 模型中定义
func (r *Role) Users() *model.ManyToMany {
    return r.ManyToMany(&User{}, "user_roles", "role_id", "user_id")
}

// 使用关联
user := NewUser()
err := user.Find(1)
roles, err := user.Roles().Get()

📝 定义关联

HasOne 关联

// 基础定义
func (u *User) Profile() *model.HasOne {
    return u.HasOne(&Profile{}, "user_id", "id")
}

// 自定义外键和本地键
func (u *User) Profile() *model.HasOne {
    return u.HasOne(&Profile{}, "owner_id", "user_id")
}

// 带默认条件
func (u *User) ActiveProfile() *model.HasOne {
    return u.HasOne(&Profile{}, "user_id", "id").
        Where("status", "=", "active")
}

HasMany 关联

// 基础定义
func (u *User) Posts() *model.HasMany {
    return u.HasMany(&Post{}, "user_id", "id")
}

// 带默认排序
func (u *User) Posts() *model.HasMany {
    return u.HasMany(&Post{}, "user_id", "id").
        OrderBy("created_at", "desc")
}

// 特定类型的文章
func (u *User) PublishedPosts() *model.HasMany {
    return u.HasMany(&Post{}, "user_id", "id").
        Where("status", "=", "published")
}

BelongsTo 关联

// 基础定义
func (p *Post) User() *model.BelongsTo {
    return p.BelongsTo(&User{}, "user_id", "id")
}

// 自定义外键
func (p *Post) Author() *model.BelongsTo {
    return p.BelongsTo(&User{}, "author_id", "id")
}

ManyToMany 关联

// 基础定义
func (u *User) Roles() *model.ManyToMany {
    return u.ManyToMany(&Role{}, "user_roles", "user_id", "role_id")
}

// 带中间表额外字段
func (u *User) Roles() *model.ManyToMany {
    return u.ManyToMany(&Role{}, "user_roles", "user_id", "role_id").
        WithPivot("assigned_at", "assigned_by")
}

// 带中间表条件
func (u *User) ActiveRoles() *model.ManyToMany {
    return u.ManyToMany(&Role{}, "user_roles", "user_id", "role_id").
        WherePivot("status", "=", "active")
}

🔍 查询关联

基础查询

user := NewUser()
err := user.Find(1)

// 获取所有文章
posts, err := user.Posts().Get()

// 获取第一篇文章
firstPost, err := user.Posts().First()

// 计数
postCount, err := user.Posts().Count()

// 检查是否存在
hasPosts, err := user.Posts().Exists()

条件查询

user := NewUser()
err := user.Find(1)

// 带条件的关联查询
recentPosts, err := user.Posts().
    Where("created_at", ">", "2023-01-01").
    Where("status", "=", "published").
    OrderBy("created_at", "desc").
    Limit(10).
    Get()

// 聚合查询
totalViews, err := user.Posts().Sum("views")
avgScore, err := user.Posts().Avg("score")

嵌套关联

// 查询用户的文章的评论
user := NewUser()
err := user.Find(1)

posts, err := user.Posts().With("Comments").Get()

// 或者使用点号语法
posts, err := user.Posts().With("Comments.User").Get()

⚡ 预加载 (Eager Loading)

基础预加载

// 预加载用户的档案
users, err := user.With("Profile").Get()

// 预加载多个关联
users, err := user.With("Profile", "Posts").Get()

// 嵌套预加载
users, err := user.With("Posts.Comments").Get()

高级预加载

// 带条件的预加载
users, err := user.With("Posts", func(q *model.HasMany) *model.HasMany {
    return q.Where("status", "=", "published").
        OrderBy("created_at", "desc").
        Limit(5)
}).Get()

// 多层嵌套预加载
users, err := user.With("Posts.Comments.User").Get()

// 计数预加载
users, err := user.WithCount("Posts", "Comments").Get()

批量预加载

// 获取用户数据
users, err := user.Where("status", "=", "active").Get()

// 批量预加载关联数据
collection := model.NewModelCollection(users)
err = collection.With("Profile", "Posts").Load()

// 现在可以访问预加载的数据而不会产生额外查询
for _, userInterface := range collection.Models() {
    if u, ok := userInterface.(*User); ok {
        profile := u.GetRelation("Profile")
        posts := u.GetRelation("Posts")
    }
}

🔧 关联操作

创建关联记录

user := NewUser()
err := user.Find(1)

// 创建关联的文章
post := &Post{
    Title:   "新文章",
    Content: "文章内容",
}
createdPost, err := user.Posts().Create(post)

// 批量创建
posts := []*Post{
    {Title: "文章1", Content: "内容1"},
    {Title: "文章2", Content: "内容2"},
}
err = user.Posts().CreateMany(posts)

关联现有记录

user := NewUser()
err := user.Find(1)

// 关联现有角色
roleIDs := []int64{1, 2, 3}
err = user.Roles().Attach(roleIDs)

// 带中间表数据的关联
err = user.Roles().AttachWithPivot(map[int64]map[string]interface{}{
    1: {"assigned_at": time.Now(), "assigned_by": "admin"},
    2: {"assigned_at": time.Now(), "assigned_by": "admin"},
})

分离关联

user := NewUser()
err := user.Find(1)

// 分离特定角色
err = user.Roles().Detach([]int64{1, 2})

// 分离所有角色
err = user.Roles().DetachAll()

同步关联

user := NewUser()
err := user.Find(1)

// 同步角色(删除不在列表中的,添加新的)
newRoleIDs := []int64{2, 3, 4}
err = user.Roles().Sync(newRoleIDs)

更新关联

user := NewUser()
err := user.Find(1)

// 更新用户的所有文章
err = user.Posts().Update(map[string]interface{}{
    "updated_at": time.Now(),
    "status":     "reviewed",
})

// 更新中间表数据
err = user.Roles().UpdatePivot(1, map[string]interface{}{
    "updated_at": time.Now(),
})

🎭 多态关联

定义多态关联

// Comment 评论模型(可以评论文章或视频)
type Comment struct {
    model.BaseModel
    ID              int64  `json:"id" db:"id"`
    Content         string `json:"content" db:"content"`
    CommentableID   int64  `json:"commentable_id" db:"commentable_id"`
    CommentableType string `json:"commentable_type" db:"commentable_type"`
}

// 在 Post 模型中定义多态关联
func (p *Post) Comments() *model.MorphMany {
    return p.MorphMany(&Comment{}, "commentable")
}

// 在 Video 模型中定义多态关联
func (v *Video) Comments() *model.MorphMany {
    return v.MorphMany(&Comment{}, "commentable")
}

// 在 Comment 模型中定义反向多态关联
func (c *Comment) Commentable() *model.MorphTo {
    return c.MorphTo("commentable")
}

使用多态关联

// 为文章创建评论
post := NewPost()
err := post.Find(1)

comment := &Comment{Content: "很好的文章!"}
err = post.Comments().Create(comment)

// 获取评论的可评论对象
comment := NewComment()
err := comment.Find(1)
commentable, err := comment.Commentable().First()

// 类型判断
switch v := commentable.(type) {
case *Post:
    fmt.Printf("评论的是文章: %s", v.Title)
case *Video:
    fmt.Printf("评论的是视频: %s", v.Title)
}

📈 性能优化

避免 N+1 查询

// 错误的做法 - 会产生 N+1 查询
users, err := user.Get() // 1 个查询
for _, u := range users {
    posts, _ := u.Posts().Get() // N 个查询
}

// 正确的做法 - 使用预加载
users, err := user.With("Posts").Get() // 2 个查询
for _, u := range users {
    posts := u.GetRelation("Posts") // 无额外查询
}

选择性加载

// 只加载需要的字段
users, err := user.
    Select("id", "name", "email").
    With("Posts:id,title,user_id").
    Get()

// 条件预加载
users, err := user.With("Posts", func(q *model.HasMany) *model.HasMany {
    return q.Where("status", "=", "published").
        Select("id", "title", "user_id").
        Limit(3)
}).Get()

关联计数

// 加载关联数量而不是关联数据
users, err := user.WithCount("Posts", "Comments").Get()

// 访问计数
for _, u := range users {
    postCount := u.GetAttribute("posts_count")
    commentCount := u.GetAttribute("comments_count")
}

// 带条件的计数
users, err := user.WithCount("Posts", func(q *model.HasMany) *model.HasMany {
    return q.Where("status", "=", "published")
}, "PublishedPostsCount").Get()

批量预加载

// 对于大量数据,使用分批预加载
users := make([]*User, 0)
err := user.Chunk(1000, func(chunk []map[string]interface{}) bool {
    // 处理每批数据
    chunkUsers := make([]*User, len(chunk))
    for i, data := range chunk {
        u := NewUser()
        u.Fill(data)
        chunkUsers[i] = u
    }
    
    // 预加载关联数据
    collection := model.NewModelCollection(chunkUsers)
    collection.With("Profile", "Posts").Load()
    
    users = append(users, chunkUsers...)
    return true
})

🔄 关联缓存

关联结果缓存

// 缓存关联查询结果
posts, err := user.Posts().Cache(5 * time.Minute).Get()

// 带标签的缓存
posts, err := user.Posts().
    CacheWithTags(5*time.Minute, "user_posts", fmt.Sprintf("user_%d", user.ID)).
    Get()

// 清除相关缓存
db.FlushCacheByTags("user_posts")

关联数据同步

// 当用户数据更新时,清除相关缓存
func (u *User) AfterUpdate() error {
    // 清除用户相关的所有缓存
    cacheKey := fmt.Sprintf("user_%d", u.ID)
    db.FlushCacheByTags(cacheKey)
    return nil
}

📚 最佳实践

1. 关联定义

// 好的做法:使用明确的方法名
func (u *User) Posts() *model.HasMany {
    return u.HasMany(&Post{}, "user_id", "id")
}

func (u *User) PublishedPosts() *model.HasMany {
    return u.HasMany(&Post{}, "user_id", "id").
        Where("status", "=", "published")
}

// 避免:在关联中进行复杂的业务逻辑

2. 性能优化

// 使用预加载避免 N+1 查询
users, err := user.With("Profile", "Posts").Get()

// 只加载需要的字段
users, err := user.
    Select("id", "name").
    With("Posts:id,title,user_id").
    Get()

// 使用关联计数代替加载完整数据
users, err := user.WithCount("Posts").Get()

3. 错误处理

// 完整的错误处理
user := NewUser()
err := user.Find(1)
if err != nil {
    return fmt.Errorf("查找用户失败: %w", err)
}

posts, err := user.Posts().Get()
if err != nil {
    return fmt.Errorf("查找用户文章失败: %w", err)
}