gorm实现多对多映射以及预加载排序
在Go语言开发中,GORM作为最流行的ORM库之一,为处理复杂的数据库关系提供了强大的支持。
多对多关系是实际业务中最常见的关联模式之一,如用户与角色、文章与标签等。
本文将深入探讨如何使用GORM实现多对多映射,并解决预加载时的排序问题。
一、多对多关系基础概念
多对多关系是指两个实体之间存在双向的一对多关系。例如,一个用户可以有多个角色,一个角色也可以被多个用户拥有。在数据库层面,这种关系需要通过中间表(连接表)来实现
让我们从一个简单的用户-角色模型开始:
package main
import (
"gorm.io/gorm"
"time"
)
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"size:100;not null"`
Email string `gorm:"uniqueIndex;size:150"`
CreatedAt time.Time
UpdatedAt time.Time
// 多对多关联:用户拥有多个角色
Roles []Role `gorm:"many2many:user_roles;"`
}
type Role struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"uniqueIndex;size:50"`
Description string `gorm:"size:200"`
CreatedAt time.Time
UpdatedAt time.Time
// 反向关联:角色属于多个用户
Users []User `gorm:"many2many:user_roles;"`
}在这个基础定义中,gorm:"many2many:user_roles;"标签告诉GORM自动创建名为user_roles的中间表
自定义中间表模型
type UserRole struct {
UserID uint `gorm:"primaryKey"` // 复合主键的一部分
RoleID uint `gorm:"primaryKey"` // 复合主键的一部分
CreatedAt time.Time // 关联创建时间
CreatedBy uint // 创建者ID
ExpiresAt *time.Time // 关联过期时间(可选)
}
// 设置自定义中间表
func setupModels(db *gorm.DB) error {
// 注册自定义中间表
err := db.SetupJoinTable(&User{}, "Roles", &UserRole{})
if err != nil {
return err
}
err = db.SetupJoinTable(&Role{}, "Users", &UserRole{})
if err != nil {
return err
}
// 自动迁移所有表
return db.AutoMigrate(&User{}, &Role{}, &UserRole{})
}带条件的多对多关系
有时我们需要在关联上添加业务条件,比如只获取有效的用户角色关系:
// 获取用户的有效角色(未过期的)
func GetUserActiveRoles(db *gorm.DB, userID uint) ([]Role, error) {
var roles []Role
now := time.Now()
err := db.Joins("JOIN user_roles ON roles.id = user_roles.role_id").
Where("user_roles.user_id = ? AND (user_roles.expires_at IS NULL OR user_roles.expires_at > ?)",
userID, now).
Order("roles.name ASC").
Find(&roles).Error
return roles, err
}二、预加载技术
预加载是 GORM 中解决 N+1 查询问题的关键特性。正确的预加载策略可以显著提升查询性能
(一)基础预加载
// 基础预加载示例
func GetUsersWithRoles(db *gorm.DB) ([]User, error) {
var users []User
// 预加载Roles关联
err := db.Preload("Roles").Find(&users).Error
if err != nil {
return nil, err
}
return users, nil
}(二)条件预加载
对于需要过滤的关联数据,可以使用条件预加载:
// 只预加载特定条件的角色
func GetUsersWithAdminRoles(db *gorm.DB) ([]User, error) {
var users []User
err := db.Preload("Roles", "name = ?", "admin").Find(&users).Error
return users, err
}(三)基础排序实现
// 预加载时对关联数据进行排序
func GetUsersWithSortedRoles(db *gorm.DB) ([]User, error) {
var users []User
err := db.Preload("Roles", func(db *gorm.DB) *gorm.DB {
// 在预加载查询中添加排序条件
return db.Order("roles.name ASC")
}).Find(&users).Error
return users, err
}(四)多字段排序
对于复杂的排序需求,可以指定多个排序字段:
func GetUsersWithComplexSortedRoles(db *gorm.DB) ([]User, error) {
var users []User
err := db.Preload("Roles", func(db *gorm.DB) *gorm.DB {
return db.Order("roles.priority DESC, roles.name ASC")
}).Find(&users).Error
return users, err
}(五)中间表字段排序
当需要根据中间表的字段进行排序时,需要使用JOIN查询:
// 根据中间表字段排序
func GetUsersWithRolesSortedByJoinTime(db *gorm.DB) ([]User, error) {
var users []User
err := db.Preload("Roles", func(db *gorm.DB) *gorm.DB {
return db.Joins("JOIN user_roles ON roles.id = user_roles.role_id").
Order("user_roles.created_at DESC")
}).Find(&users).Error
return users, err
}(六)复杂场景的预加载排序
在实际业务中,我们经常会遇到更复杂的排序需求。以下是几个常见场景的解决方案。
多对多嵌套预加载排序
当存在多层关联关系时,需要对每一层都进行排序:
type Permission struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"size:100"`
Code string `gorm:"uniqueIndex;size:50"`
}
// 角色与权限的多对多关系
type RolePermission struct {
RoleID uint `gorm:"primaryKey"`
PermissionID uint `gorm:"primaryKey"`
GrantedAt time.Time
}
func SetupNestedModels(db *gorm.DB) error {
// 添加权限多对多关系
err := db.SetupJoinTable(&Role{}, "Permissions", &RolePermission{})
if err != nil {
return err
}
return db.AutoMigrate(&Permission{}, &RolePermission{})
}
// 嵌套预加载排序
func GetUsersWithRolesAndPermissions(db *gorm.DB) ([]User, error) {
var users []User
err := db.Preload("Roles", func(db *gorm.DB) *gorm.DB {
return db.Order("roles.priority DESC").Preload("Permissions", func(db *gorm.DB) *gorm.DB {
return db.Order("permissions.name ASC")
})
}).Find(&users).Error
return users, err
}动态排序参数
对于需要根据用户输入动态排序的场景:
type SortParams struct {
RoleSortField string
RoleSortOrder string
PermissionSortField string
PermissionSortOrder string
}
func GetUsersWithDynamicSorting(db *gorm.DB, params SortParams) ([]User, error) {
var users []User
err := db.Preload("Roles", func(db *gorm.DB) *gorm.DB {
orderClause := fmt.Sprintf("roles.%s %s", params.RoleSortField, params.RoleSortOrder)
return db.Order(orderClause).Preload("Permissions", func(db *gorm.DB) *gorm.DB {
permissionOrder := fmt.Sprintf("permissions.%s %s",
params.PermissionSortField, params.PermissionSortOrder)
return db.Order(permissionOrder)
})
}).Find(&users).Error
return users, err
}注意⚠️:不正确的预加载和排序可能导致严重的性能问题
GORM的多对多关系处理虽然功能强大,但也需要根据具体业务需求进行合理设计和优化。希望本文能为您的Go项目开发提供实用的参考和指导。
版权声明
未经授权,禁止转载本文章。
如需转载请保留原文链接并注明出处。即视为默认获得授权。
未保留原文链接未注明出处或删除链接将视为侵权,必追究法律责任!
本文原文链接: https://fiveyoboy.com/articles/go-gorm-preload-sort/
备用原文链接: https://blog.fiveyoboy.com/articles/go-gorm-preload-sort/