# Many To Many
## Many To Many
Many to Many 会在两个 model 中添加一张连接表。
例如,您的应用包含了 user 和 language,且一个 user 可以说多种 language,多个 user 也可以说一种 language。
```go
// User 拥有并属于多种 language,`user_languages` 是连接表
type User struct {
gorm.Model
Languages []Language `gorm:"many2many:user_languages;"`
}
type Language struct {
gorm.Model
Name string
}
```
当使用 GORM 的 `AutoMigrate` 为 `User` 创建表时,GORM 会自动创建连接表
## 反向引用
```go
// 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;"`
}
```
## 重写外键
对于 `many2many` 关系,连接表会同时拥有两个模型的外键,例如:
```go
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
```
若要重写它们,可以使用标签 `foreignKey`、`reference`、`joinforeignKey`、`joinReferences`。当然,您不需要使用全部的标签,你可以仅使用其中的一个重写部分的外键、引用。
```go
type User struct {
gorm.Model
Profiles []Profile `gorm:"many2many:user_profiles;foreignKey:Refer;joinForeignKey:UserReferID;References:UserRefer;JoinReferences:ProfileRefer"`
Refer uint
}
type Profile struct {
gorm.Model
Name string
UserRefer uint
}
// Which creates join table: user_profiles
// foreign key: user_refer_id, reference: users.refer
// foreign key: profile_refer, reference: profiles.user_refer
```
## 自引用 Many2Many
自引用 many2many 关系
```go
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
```
## 预加载
GORM 允许通过 `Preload` 预加载 has many 关联的记录,查看 [预加载](./preload.md) 获取详情
## Many2Many 的 CURD
查看 [关联模式](./associations.md#Association-Mode) 获取 many2many 相关的用法
## 自定义连接表
`连接表` 可以是一个全功能的模型,支持 `Soft Delete`、`钩子`、定义更多的字段,就跟其它模型一样。您可以通过 `SetupJoinTable` 指定它,例如:
```go
type Person struct {
ID int
Name string
Addresses []Address `gorm:"many2many:person_addresses;"`
}
type Address struct {
ID uint
Name string
}
type PersonAddress struct {
PersonID int
AddressID int
CreatedAt time.Time
DeletedAt gorm.DeletedAt
}
func (PersonAddress) BeforeCreate(db *gorm.DB) error {
// ...
}
// PersonAddress 必须定义相关的的外键字段,否则会报错
db.SetupJoinTable(&Person{}, "Addresses", &PersonAddress{})
```
## 外键约束
你可以通过标签 `constraint` 并带上 `OnUpdate`、`OnDelete` 实现外键约束,例如:
```go
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);
```
## 复合外键
如果您的模型使用了 [复合主键](../高级主题/composite_primary_key.md),GORM 会默认启用复合外键。
您也可以覆盖默认的外键、指定多个外键,只需用逗号分隔那些键名,例如:
```go
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;"`
SharedTags []Tag `gorm:"many2many:shared_blog_tags;ForeignKey:id;References:id"`
LocaleTags []Tag `gorm:"many2many:locale_blog_tags;ForeignKey:id,locale;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: shared_blog_tags
// foreign key: blog_id, reference: blogs.id
// foreign key: tag_id, reference: tags.id
// 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
```
还可以查看 [复合主键](../高级主题/composite_primary_key.md)