说人话的 GORM文档 之多对多的反向引用

48 min read

GORM 那傻逼文档真是傻逼妈妈给傻逼儿子开门,傻逼到家了

Many to Many 会在两个 model 中添加一张连接表。

例如,您的应用包含了 user 和 language,且一个 user 可以说多种 language,多个 user 也可以说一种 language。

// User 拥有并属于多种 language,`user_languages` 是连接表
type User struct {
  gorm.Model
  Languages []Language `gorm:"many2many:user_languages;"`
}

type Language struct {
  gorm.Model
  Name string
}

当使用 GORM 的 AutoMigrateUser 创建表时,GORM 会自动创建连接表

反向引用

// User 拥有并属于多种 language,`user_languages` 是连接表
type User struct {
  gorm.Model
  Languages []*Language `gorm:"many2many:user_languages;"`
}

type Language struct {
  gorm.Model
  Name string
  Users []*User `gorm:"many2many:user_languages;"`
}

在这个例子中,我们有两个实体:UserLanguage。一个 User 可以说多种 Language,而多个 User 也可以说同一种 Language。在这种情况下,我们使用了 GORM 的 "多对多" 关联。通过在实体结构中添加 many2many 标签,我们可以定义这种关系。

当使用 GORM 的 AutoMigrateUserLanguage 创建表时,GORM 会自动创建一个名为 user_languages 的连接表。这个表将记录 UserLanguage 之间的关系。连接表的结构如下:

user_languages 表:

字段名 字段类型 示例值
user_id int 1
language_id int 2

在上面的示例中,我们在 Language 结构中添加了一个反向引用 Users,它指向与该 Language 关联的所有 User。这样,我们可以在查询时方便地获取与特定语言关联的所有用户。

例如,如果你想获取说英语(ID 为 1)的所有用户,你可以这样做:

var english Language
db.Preload("Users").Find(&english, "id = ?", 1)

这个查询将预加载与 english 关联的所有用户。你可以通过访问 english.Users 来获取它们。通过添加反向引用,你可以更轻松地在 UserLanguage 之间导航和查询相关数据。

如果不加反向引用呢?

如果不在 Language 结构中加入反向引用,那么你将不能直接从 Language 结构预加载与之关联的所有 User。但是,你仍然可以通过编写稍微复杂的查询来获取同样的信息。

例如,在不使用反向引用的情况下,要获取说英语(ID 为 1)的所有用户,你可以使用以下查询:

var users []User
db.Joins("JOIN user_languages ON user_languages.user_id = users.id").
   Where("user_languages.language_id = ?", 1).
   Find(&users)

这个查询会执行一个连接操作(JOIN),通过 user_languages 表来关联 UserLanguage。然后,它会过滤出说英语(language_id 为 1)的用户。虽然这样的查询稍微复杂一些,但在不使用反向引用的情况下,仍然可以获取所需的数据。

查询方法

// 检索 User 列表并预加载 Language
func GetAllUsers(db *gorm.DB) ([]User, error) {
    var users []User
    err := db.Model(&User{}).Preload("Languages").Find(&users).Error
    return users, err
}

// 检索 Language 列表并预加载 User
func GetAllLanguages(db *gorm.DB) ([]Language, error) {
    var languages []Language
    err := db.Model(&Language{}).Preload("Users").Find(&languages).Error
    return languages, err
}

新增方法

要修改多对多关系,您可以使用 Association 函数。例如,要为名为 “Alice” 的用户添加两种语言 “English” 和 “Spanish”,您可以这样做:

var user User
db.Where("name = ?", "Alice").First(&user)

english := Language{Name: "English"}
spanish := Language{Name: "Spanish"}

db.Model(&user).Association("Languages").Append([]Language{english, spanish})

上面的代码会在数据库中查找名为 “Alice” 的用户。如果找到了,它会将查询结果保存到 user 变量中。然后,它会在数据库中查找名为 “English” 和 “Spanish” 的语言。如果找到了,它会在连接表 user_languages 中插入两条新记录来表示关系。

更新方法

要更新多对多关系,您可以使用 Association 函数。例如,要将名为 “Alice” 的用户的语言 “English” 更改为 “Chinese”,您可以这样做:

var user User
db.Where("name = ?", "Alice").First(&user)

english := Language{Name: "English"}
chinese := Language{Name: "Chinese"}

db.Model(&user).Association("Languages").Replace([]Language{english}, []Language{chinese})

上面的代码会在数据库中查找名为 “Alice” 的用户。如果找到了,它会将查询结果保存到 user 变量中。然后,它会在连接表 user_languages 中删除与用户 “Alice” 和语言 “English” 相关联的记录,并插入一条新记录来表示用户 “Alice” 和语言 “Chinese” 的关系。

清空多对多关系

要清空多对多关系,您可以使用 Association 函数。例如,要清空名为 “Alice” 的用户的所有语言,您可以这样做:

var user User
db.Where("name = ?", "Alice").First(&user)

db.Model(&user).Association("Languages").Clear()

上面的代码会在数据库中查找名为 “Alice” 的用户。如果找到了,它会将查询结果保存到 user 变量中。然后,它会在连接表 user_languages 中删除与用户 “Alice” 相关联的所有记录。