多对多

原文:https://gorm.io/docs/many_to_many.html

多对多 Many To Many

Many to Many add a join table between two models.

​ 多对多在两个模型之间添加一个连接表。

For example, if your application includes users and languages, and a user can speak many languages, and many users can speak a specified language.

​ 例如,如果你的应用包括用户和语言,用户可以说多种语言,也可以说多种语言。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// User 有且属于多种语言,`user_languages` 是连接表 User has and belongs to many languages, `user_languages` is the join table
type User struct {
  gorm.Model
  Languages []Language `gorm:"many2many:user_languages;"`
}

type Language struct {
  gorm.Model
  Name string
}

When using GORM AutoMigrate to create a table for User, GORM will create the join table automatically

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

反向引用 Back-Reference

声明 Declare

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// User 有且属于多种语言,使用 `user_languages` 作为连接表 User has and belongs to many languages, use `user_languages` as join table
type User struct {
  gorm.Model
  Languages []*Language `gorm:"many2many:user_languages;"`
}

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

检索 Retrieve

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
//  检索带有预加载语言的用户列表 Retrieve user list with eager loading languages
func GetAllUsers(db *gorm.DB) ([]User, error) {
  var users []User
  err := db.Model(&User{}).Preload("Languages").Find(&users).Error
  return users, err
}

// 检索带有预加载用户的编程语言列表 Retrieve language list with eager loading users
func GetAllLanguages(db *gorm.DB) ([]Language, error) {
  var languages []Language
  err := db.Model(&Language{}).Preload("Users").Find(&languages).Error
  return languages, err
}

覆盖外键 Override Foreign Key

For a many2many relationship, the join table owns the foreign key which references two models, for example:

​ 对于 many2many 关系,连接表拥有引用两个模型的外键,例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
type User struct {
  gorm.Model
  Languages []Language `gorm:"many2many:user_languages;"`
}

type Language struct {
  gorm.Model
  Name string
}

// Join Table: user_languages
//   foreign key: user_id, reference: users.id
//   foreign key: language_id, reference: languages.id

To override them, you can use tag foreignKey, references, joinForeignKey, joinReferences, not necessary to use them together, you can just use one of them to override some foreign keys/references

​ 要覆盖它们,可以使用标签 foreignKeyreferencesjoinForeignKeyjoinReferences,不一定需要同时使用它们,只需使用其中一个来覆盖某些外键/引用即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
type User struct {
  gorm.Model
  Profiles []Profile `gorm:"many2many:user_profiles;foreignKey:Refer;joinForeignKey:UserReferID;References:UserRefer;joinReferences:ProfileRefer"`
  Refer    uint      `gorm:"index:,unique"`
}

type Profile struct {
  gorm.Model
  Name      string
  UserRefer uint `gorm:"index:,unique"`
}

// Which creates join table: user_profiles
//   foreign key: user_refer_id, reference: users.refer
//   foreign key: profile_refer, reference: profiles.user_refer

NOTE: Some databases only allow create database foreign keys that reference on a field having unique index, so you need to specify the unique index tag if you are creating database foreign keys when migrating

注意: 一些数据库只允许创建指向具有唯一索引的字段的数据库外键,因此如果您在迁移时创建数据库外键,您需要指定 unique index 标签。

自引用多对多 Self-Referential Many2Many

Self-referencing many2many relationship

​ 自引用多对多关系

1
2
3
4
5
6
7
8
type User struct {
  gorm.Model
  Friends []*User `gorm:"many2many:user_friends"`
}

// Which creates join table: user_friends
//   foreign key: user_id, reference: users.id
//   foreign key: friend_id, reference: users.id

预加载(Eager loading)Eager Loading

GORM allows eager loading has many associations with Preload, refer Preloading (Eager loading) for details

​ GORM 允许使用 Preload 进行预加载具有许多关联的 many2many。有关详细信息,请参阅 预加载(Eager loading)

与 many2many 关系的 CRUD CRUD with Many2Many

Please checkout Association Mode for working with many2many relations

​ 请查看 Association Mode 以处理 many2many 关系。

Customize JoinTable

JoinTable can be a full-featured model, like having Soft DeleteHooks supports and more fields, you can set it up with SetupJoinTable, for example:

JoinTable 可以是一个完整的功能模型,如具有 Soft DeleteHooks 支持和更多字段的模型,您可以使用 SetupJoinTable 设置它,例如:

NOTE: Customized join table’s foreign keys required to be composited primary keys or composited unique index

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
type Person struct {
  ID        int
  Name      string
  Addresses []Address `gorm:"many2many:person_addressses;"`
}

type Address struct {
  ID   uint
  Name string
}

type PersonAddress struct {
  PersonID  int `gorm:"primaryKey"`
  AddressID int `gorm:"primaryKey"`
  CreatedAt time.Time
  DeletedAt gorm.DeletedAt
}

func (PersonAddress) BeforeCreate(db *gorm.DB) error {
  // ...
}

// Change model Person's field Addresses' join table to PersonAddress
// PersonAddress must defined all required foreign keys or it will raise error
err := db.SetupJoinTable(&Person{}, "Addresses", &PersonAddress{})

FOREIGN KEY Constraints

You can setup OnUpdate, OnDelete constraints with tag constraint, it will be created when migrating with GORM, for example:

​ 您可以使用标签 constraint 设置 OnUpdateOnDelete 约束,它在迁移时由 GORM 创建,例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
type User struct {
  gorm.Model
  Languages []Language `gorm:"many2many:user_speaks;"`
}

type Language struct {
  Code string `gorm:"primarykey"`
  Name string
}

// 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);

You are also allowed to delete selected many2many relations with Select when deleting, checkout Delete with Select for details

Composite Foreign Keys

If you are using Composite Primary Keys for your models, GORM will enable composite foreign keys by default

​ 如果您使用 Composite Primary Keys 为您的模型,GORM 默认情况下会启用复合外键。

You are allowed to override the default foreign keys, to specify multiple foreign keys, just separate those keys’ name by commas, for example:

​ 您还可以覆盖默认的外键,以指定多个外键,只需用逗号分隔这些键的名称即可,例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
type Tag struct {
  ID     uint   `gorm:"primaryKey"`
  Locale string `gorm:"primaryKey"`
  Value  string
}

type Blog struct {
  ID         uint   `gorm:"primaryKey"`
  Locale     string `gorm:"primaryKey"`
  Subject    string
  Body       string
  Tags       []Tag `gorm:"many2many:blog_tags;"`
  LocaleTags []Tag `gorm:"many2many:locale_blog_tags;ForeignKey:id,locale;References:id"`
  SharedTags []Tag `gorm:"many2many:shared_blog_tags;ForeignKey:id;References:id"`
}

// Join Table: blog_tags
//   foreign key: blog_id, reference: blogs.id
//   foreign key: blog_locale, reference: blogs.locale
//   foreign key: tag_id, reference: tags.id
//   foreign key: tag_locale, reference: tags.locale

// Join Table: locale_blog_tags
//   foreign key: blog_id, reference: blogs.id
//   foreign key: blog_locale, reference: blogs.locale
//   foreign key: tag_id, reference: tags.id

// Join Table: shared_blog_tags
//   foreign key: blog_id, reference: blogs.id
//   foreign key: tag_id, reference: tags.id

Also check out Composite Primary Keys

​ 请查看复合主键

最后修改 October 10, 2024: 更新 (a4b8f85)