跳至内容

GORM 使用指南

GORM 是 Go 语言中最流行的 ORM(对象关系映射)库,它通过结构体标签将 Go 类型映射到数据库表,让开发者无需手写 SQL 即可完成增删改查、关联查询、事务管理等常见操作。

本文以图书管理系统为贯穿示例,覆盖 GORM 的核心关联关系(Belongs To、Has One、Has Many、Many To Many、Polymorphism),以及预加载、级联操作、关联模式等进阶技巧。读完本文,你将能够正确定义模型间的各类关联,并掌握避免 N+1 查询的实用方法。

关联关系

我们以图书管理系统为例,介绍GORM中的关联关系

在图书管理系统中,常见的实体包括:Author(作者)、Book(图书)、Publisher(出版社)、User(用户)、BorrowRecord(借阅记录)等

下面我们分别详细说明并给出示例。

注意:GORM中,外键默认使用主表的类型名(小写)加上ID构成,例如Publisher的默认外键是PublisherID。我们可以通过标签自定义。

示例:图书管理系统模型定义

我们将定义以下模型:

  • Publisher (出版社)
  • Author (作者)
  • Book (图书)
  • Tag (标签)
  • User (用户)
  • LibraryCard (借书卡,与用户一对一)
  • BorrowRecord (借阅记录,用户和图书之间多对多,但借阅记录需要记录额外信息如借出日期和归还日期,所以我们会通过中间表BorrowRecord来实现多对多)
  • Comment (评论,多态,可以评论图书或者期刊)

注意:这里我们使用GORM标签来定义关联关系。

Belongs To(属于)

表示一个模型属于另一个模型。例如,每本书属于一个出版社,那么Book模型中会有一个PublisherID字段作为外键。

  • 定义:子模型从属于父模型,外键存储在子表中
  • 适用场景:书籍属于特定出版社(外键在 books 表中)
// Publisher 出版社表
type Publisher struct {
    ID   uint   `gorm:"primaryKey"`
    Name string `gorm:"unique"`
}

// 书籍表
type Book struct {
    ID           uint
    Title        string
    PublisherID  uint           // 外键字段(命名规则:父模型名+ID)
    Publisher    Publisher      `gorm:"foreignKey:PublisherID;references:ID"` // 显式声明关联
}


外键规则
	1默认外键命名父结构体名 + ID PublisherID
	2自定义gorm:"foreignKey:CustomForeignKey"
	3指向父表主键references:ParentIDField


// 操作示例
// 查询书籍及其出版社
var book Book
db.Preload("Publisher").First(&book, 1)

// 创建关联
publisher := Publisher{Name: "人民文学出版社"}
db.Create(&Book{Title: "三国演义", Publisher: publisher})

Has One(一对一)

表示一个模型拥有另一个模型。例如,一个用户有一个借书卡(LibraryCard),但借书卡只能属于一个用户。

  • 定义:父模型拥有唯一的子模型,外键存储在子表中
  • 适用场景:用户拥有唯一的借书卡
// User 用户表
type User struct {
    ID       uint
    Name     string
    LibraryCard LibraryCard `gorm:"constraint:OnDelete:CASCADE;"` // 级联删除
}

// LibraryCard 借书卡
type LibraryCard struct {
    ID        uint
    CardNumber string `gorm:"unique"`
    UserID    uint    // 外键(命名规则相同)
}

区别 Belongs To

  • 方向性:从父模型角度声明
  • 控制权:父模型控制关联

Has Many(一对多)

表示一个模型拥有多个另一个模型。例如,一个作者有多本书(Author has many Books)。

  • 定义:父模型拥有多个子模型,外键在子表中
  • 适用场景:一位作者拥有多本书籍
// Author 作者表
type Author struct {
    ID    uint
    Name  string
    Books []Book `gorm:"foreignKey:AuthorID"` // 自定义外键
}

// Book 书籍表
type Book struct {
    ID       uint
    Title    string
    AuthorID uint // 外键
}

// 操作示例
// 查询作者及其所有书籍
author := Author{}
db.Preload("Books").First(&author, 1)

// 批量创建关联
author.Books = []Book{
    {Title: "围城"}, 
    {Title: "谈艺录"},
}
db.Create(&author)

Many To Many(多对对)

表示两个模型可以相互拥有多个。例如,一本书可以有多个标签(Tag),一个标签也可以被多本书使用。

  • 定义:通过中间表建立双向关联
  • 适用场景:书籍属于多个分类,分类包含多本书
// Book 书籍表
type Book struct {
    ID      uint
    Title   string
    Genres  []Genre `gorm:"many2many:book_genres;"` // 自定义连接表
}

// Genre 分类
type Genre struct {
    ID    uint
    Name  string
    Books []Book `gorm:"many2many:book_genres;"`
}

// 中间表表结构
type BookGenre struct {
    BookID  uint `gorm:"primaryKey"`
    GenreID uint `gorm:"primaryKey"`
    CreatedAt time.Time
}

// 操作示例
// 添加书籍到分类
db.Model(&book).Association("Genres").Append(&Genre{Name: "文学"})

// 查询某分类下所有书籍
var genres []Genre
db.Preload("Books").Where("name = ?", "科幻").Find(&genres)

Polymorphism(多态)

允许一个模型同时属于多个其他模型。例如,评论(Comment)可以属于一本书(Book)或者一篇期刊(Journal),通过一个字段标识属于哪种类型。

  • 定义:单个模型可关联多种父模型
  • 适用场景:评论可以针对书籍或者作者
// Comment 评论表
type Comment struct {
    ID             uint
    Content        string
    CommentableID  uint      // 多态ID
    CommentableType string   // 目标模型类型(book/author)
}

// Book 书籍表
type Book struct {
    ID       uint
    Title    string
    Comments []Comment `gorm:"polymorphic:Commentable;"` // 多态声明
}

// Author 作者表
type Author struct {
    ID       uint
    Name     string
    Comments []Comment `gorm:"polymorphic:Commentable;"`
}

// 操作示例
// 获取书籍的所有评论
var book Book
db.First(&book, 123)
db.Model(&book).Association("Comments").Find(&comments)

关联技巧

预加载优化

// 避免 N+1 查询
db.Preload("Authors.Books.Genres").Find(&libraries)

// 条件预加载
db.Preload("Books", "rating > ?", 4.5).Find(&authors)

级联操作

type Author struct {
    gorm.Model
    Books []Book `gorm:"constraint:OnDelete:SET NULL;"`
}

关联模式操作

// 直接操作关联关系
db.Model(&author).Association("Books").Append(
    &Book{Title: "新书"}, 
    &Book{Title: "旧作"}
)

// 清空关联
db.Model(&book).Association("Genres").Clear()

连接表自定义属性

db.SetupJoinTable(&Book{}, "Genres", &BookGenre{})

JSON类型支持

https://github.com/go-gorm/datatypes
最后更新于