Many To Many

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

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

  1. // User 拥有并属于多种 language,`user_languages` 是连接表
  2. type User struct {
  3. gorm.Model
  4. Languages []Language `gorm:"many2many:user_languages;"`
  5. }
  6. type Language struct {
  7. gorm.Model
  8. Name string
  9. }

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

反向引用

声明

  1. // User 拥有并属于多种 language,`user_languages` 是连接表
  2. type User struct {
  3. gorm.Model
  4. Languages []*Language `gorm:"many2many:user_languages;"`
  5. }
  6. type Language struct {
  7. gorm.Model
  8. Name string
  9. Users []*User `gorm:"many2many:user_languages;"`
  10. }

检索

  1. // 检索 User 列表并预加载 Language
  2. func GetAllUsers(db *gorm.DB) ([]User, error) {
  3. var users []User
  4. err := db.Model(&User{}).Preload("Languages").Find(&users).Error
  5. return users, err
  6. }
  7. // 检索 Language 列表并预加载 User
  8. func GetAllLanguages(db *gorm.DB) ([]Language, error) {
  9. var languages []Language
  10. err := db.Model(&Language{}).Preload("Users").Find(&languages).Error
  11. return languages, err
  12. }

重写外键

对于 many2many 关系,连接表会同时拥有两个模型的外键,例如:

  1. type User struct {
  2. gorm.Model
  3. Languages []Language `gorm:"many2many:user_languages;"`
  4. }
  5. type Language struct {
  6. gorm.Model
  7. Name string
  8. }
  9. // 连接表:user_languages
  10. // foreign key: user_id, reference: users.id
  11. // foreign key: language_id, reference: languages.id

若要重写它们,可以使用标签 foreignKeyreferencesjoinforeignKeyjoinReferences。当然,您不需要使用全部的标签,你可以仅使用其中的一个重写部分的外键、引用。

  1. type User struct {
  2. gorm.Model
  3. Profiles []Profile `gorm:"many2many:user_profiles;foreignKey:Refer;joinForeignKey:UserReferID;References:UserRefer;joinReferences:ProfileRefer"`
  4. Refer uint `gorm:"index:,unique"`
  5. }
  6. type Profile struct {
  7. gorm.Model
  8. Name string
  9. UserRefer uint `gorm:"index:,unique"`
  10. }
  11. // 会创建连接表:user_profiles
  12. // foreign key: user_refer_id, reference: users.refer
  13. // foreign key: profile_refer, reference: profiles.user_refer

注意: 某些数据库只允许在唯一索引字段上创建外键,如果您在迁移时会创建外键,则需要指定 unique index 标签。

自引用 Many2Many

自引用 many2many 关系

  1. type User struct {
  2. gorm.Model
  3. Friends []*User `gorm:"many2many:user_friends"`
  4. }
  5. // 会创建连接表:user_friends
  6. // foreign key: user_id, reference: users.id
  7. // foreign key: friend_id, reference: users.id

预加载

GORM 可以通过 Preload 预加载 has many 关联的记录,查看 预加载 获取详情

Many2Many 的 CURD

查看 关联模式 获取 many2many 相关的用法

自定义连接表

连接表 可以是一个功能齐全的模型,比如支持 软删除钩子函数功能,并且可以具有更多字段。您可以通过 SetupJoinTable 设置,例如:

注意: 自定义连接表要求外键是复合主键或复合唯一索引

  1. type Person struct {
  2. ID int
  3. Name string
  4. Addresses []Address `gorm:"many2many:person_addresses;"`
  5. }
  6. type Address struct {
  7. ID uint
  8. Name string
  9. }
  10. type PersonAddress struct {
  11. PersonID int `gorm:"primaryKey"`
  12. AddressID int `gorm:"primaryKey"`
  13. CreatedAt time.Time
  14. DeletedAt gorm.DeletedAt
  15. }
  16. func (PersonAddress) BeforeCreate(db *gorm.DB) error {
  17. // ...
  18. }
  19. // 修改 Person 的 Addresses 字段的连接表为 PersonAddress
  20. // PersonAddress 必须定义好所需的外键,否则会报错
  21. err := db.SetupJoinTable(&Person{}, "Addresses", &PersonAddress{})

外键约束

你可以通过为标签 constraint 配置 OnUpdateOnDelete 实现外键约束,在使用 GORM 进行迁移时它会被创建,例如:

  1. type User struct {
  2. gorm.Model
  3. Languages []Language `gorm:"many2many:user_speaks;"`
  4. }
  5. type Language struct {
  6. Code string `gorm:"primarykey"`
  7. Name string
  8. }
  9. // CREATE TABLE `user_speaks` (`user_id` integer,`language_code` text,PRIMARY KEY (`user_id`,`language_code`),CONSTRAINT `fk_user_speaks_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,CONSTRAINT `fk_user_speaks_language` FOREIGN KEY (`language_code`) REFERENCES `languages`(`code`) ON DELETE SET NULL ON UPDATE CASCADE);

你也可以在删除记录时通过 Select 来删除 many2many 关系的记录,查看 Delete with Select 获取详情

复合外键

如果您的模型使用了 复合主键,GORM 会默认启用复合外键。

您也可以覆盖默认的外键、指定多个外键,只需用逗号分隔那些键名,例如:

  1. type Tag struct {
  2. ID uint `gorm:"primaryKey"`
  3. Locale string `gorm:"primaryKey"`
  4. Value string
  5. }
  6. type Blog struct {
  7. ID uint `gorm:"primaryKey"`
  8. Locale string `gorm:"primaryKey"`
  9. Subject string
  10. Body string
  11. Tags []Tag `gorm:"many2many:blog_tags;"`
  12. LocaleTags []Tag `gorm:"many2many:locale_blog_tags;ForeignKey:id,locale;References:id"`
  13. SharedTags []Tag `gorm:"many2many:shared_blog_tags;ForeignKey:id;References:id"`
  14. }
  15. // 连接表:blog_tags
  16. // foreign key: blog_id, reference: blogs.id
  17. // foreign key: blog_locale, reference: blogs.locale
  18. // foreign key: tag_id, reference: tags.id
  19. // foreign key: tag_locale, reference: tags.locale
  20. // 连接表:locale_blog_tags
  21. // foreign key: blog_id, reference: blogs.id
  22. // foreign key: blog_locale, reference: blogs.locale
  23. // foreign key: tag_id, reference: tags.id
  24. // 连接表:shared_blog_tags
  25. // foreign key: blog_id, reference: blogs.id
  26. // foreign key: tag_id, reference: tags.id

查看 复合主键 获取详情