gorm day8

  • gorm Has Many关系
  • gorm Many To Many关系

gorm Has Many关系

Has Many

在GORM(Go的一个对象关系映射库)中,“Has Many” 关系表示一个实体与另一个实体之间的一对多关系。这意味着一个实体(我们称之为"父"实体)可以拥有指向多个其他实体("子"实体)的引用。这种关系在数据库中通常通过使用外键在"子"实体上来实现。

举个例子来说明GORM中定义和使用的Has Many关系:
示例:用户和订单
假设我们有两个模型:User 和 Order。一个用户可以拥有多个订单,但每个订单只能属于一个用户。这是一个典型的"Has Many"关系。

package main

import (
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
)

type User struct {
    gorm.Model
    Name   string
    Orders []Order // Has Many 关系
}

type Order struct {
    gorm.Model
    ProductName string
    UserID      uint // 外键
}

func main() {
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    if err != nil {
        panic("failed to connect database")
    }

    // 自动迁移模式
    db.AutoMigrate(&User{}, &Order{})

    // 创建用户和订单
    user := User{Name: "John Doe", Orders: []Order{
        {ProductName: "Book"},
        {ProductName: "Pen"},
    }}

    db.Create(&user) // GORM 会自动处理外键关系
}

定义模型:我们定义了两个结构体 User 和 Order 来作为我们的模型。User 结构体中包含一个 Orders 字段,它是一个 Order 结构体切片,表示一个用户可以有多个订单。
外键:在 Order 结构体中,UserID 字段作为外键,用来存储创建该订单的用户的ID。这个字段告诉GORM这两个模型之间的关联方式。
自动迁移:db.AutoMigrate(&User{}, &Order{}) 告诉GORM自动创建或修改数据库表以匹配模型的结构。这包括设置正确的外键关系。
创建记录:当我们创建一个 User 实例并设置其 Orders 字段时,GORM知道如何将订单记录插入 orders 表,并设置每个订单的 UserID 字段,以反映它们属于该用户。

文档例子
has many与另一个模型建立了一对多的连接。不同于has one,拥有者可以有零或多个关联模型。
例如,您的应用包含user和credit card模型,且每个user可以有多张credit card。

// User 有多张 CreditCard,UserID 是外键
type User struct {
  gorm.Model
  CreditCards []CreditCard
}

type CreditCard struct {
  gorm.Model
  Number string
  UserID uint
}

这个看起来就还是很直观的。之间的关系还是很清楚的。
User结构体:代表应用中的用户。它继承了gorm.Model,这是GORM提供的基础模型,包含了一些常用字段,如ID, CreatedAt, UpdatedAt, DeletedAt。User结构体中包含一个CreditCards字段,这是一个CreditCard结构体的切片。这表明一个用户可以关联多张信用卡。GORM通过这个字段理解到User和CreditCard之间存在一对多关系。

CreditCard结构体:代表信用卡。同样继承了gorm.Model,并且有一个Number字段存储信用卡号,以及一个UserID字段。UserID是一个无符号整型,用于存储这张信用卡所属用户的ID。这个字段是实现一对多关系的外键,它指向User表的主键ID。

关系和外键
在这个关系中,UserID字段在CreditCard结构体内充当外键,指向User表的ID字段。这表示每张CreditCard都属于一个特定的User。

当使用GORM进行数据库操作(如创建、查询)时,GORM会自动处理这些关联关系。例如,当你为一个User添加多张CreditCard并保存到数据库时,GORM会自动填充每张CreditCard的UserID字段,确保关系的正确性。

重写外键

要定义has many关系,通用必须存在外键。默认的外键名是拥有者的类型名加上其主键字段名。
例如,要定义一个属于User的模型,则其外键应该是UserID。
此外,想要使用另一个字段作为外键,你可以使用foreignKey标签自定义它:

type User struct {
  gorm.Model
  CreditCards []CreditCard `gorm:"foreignKey:UserRefer"`
}

type CreditCard struct {
  gorm.Model
  Number    string
  UserRefer uint
}

解读:
在GORM中定义"Has Many"关系时,确实需要有一个外键存在于"子"模型中,用以指向"父"模型的主键,从而表明两者之间的关联。这段内容详细解释了如何在GORM中设置和自定义这种一对多(“Has Many”)关系,以及如何指定外键。

默认外键命名规则
GORM的默认行为是根据"父"模型的类型名和其主键字段名来自动生成外键名。例如,如果有一个模型叫User(父模型),其主键字段名默认是ID(因为gorm.Model包含一个名为ID的字段作为主键),那么属于User的任何模型(子模型)的默认外键名将会是UserID。

自定义外键
管GORM提供了一个默认的外键命名规则,但在某些情况下,默认规则可能不适合你的数据库设计。可能你想要的外键名与默认提供的不同,或者你的数据库已经有了既定的外键命名规范。为了满足这种需求,GORM允许通过foreignKey标签来自定义外键字段。

示例解读
在给定的示例中,User模型和CreditCard模型之间存在一个"Has Many"关系,即一个用户可以拥有多张信用卡。示例通过在User结构体中的CreditCards字段旁使用gorm:"foreignKey:UserRefer"标签明确指定了CreditCard模型使用UserRefer字段作为外键,而不是默认的UserID。

这意味着:
User模型通过CreditCards字段关联多张CreditCard。
在CreditCard模型中,UserRefer字段被用作外键,指向User模型的主键ID字段。这个"指向"的意思是,子表中的外键字段的值对应于父表中某条记录的主键值。
这种关系允许GORM在操作数据库时自动处理这些关联,比如在查询用户时同时获取其所有信用卡信息。

重写引用

GORM 通常使用使用者的主键作为外键的值。对于上面的例子,它是User的ID字段。
为user添加credit card时,GORM会将user的ID字段保存到credit card的UserID字段。
同样的,您也可以使用标签references来更改它,例如:

type User struct {
  gorm.Model
  MemberNumber string
  CreditCards  []CreditCard `gorm:"foreignKey:UserNumber;references:MemberNumber"`
}

type CreditCard struct {
  gorm.Model
  Number     string
  UserNumber string
}

这里我主要说说references
references:MemberNumber是GORM中用于自定义外键关联的一个标签,它指定了在建立模型间关系时,应该引用的字段。 当你在GORM中定义模型关系时,foreignKey和references标签通常一起使用来指明这种自定义关系。 让我来具体解释一下references:MemberNumber的作用和意义。

默认外键引用
在没有明确指定references标签的情况下,GORM默认使用"父"模型的主键字段(通常是ID字段)作为外键关联的引用。这意味着,在一对多(Has Many)或一对一(Has One)关系中,"子"模型中的外键字段将会存储与"父"模型的ID字段相对应的值。

自定义外键引用
使用references标签,你可以指定一个不同于"父"模型的ID字段的其他字段作为外键引用。这在你想要建立模型间关系,但又不想使用默认的ID字段作为关联依据时非常有用。

例子解读:
references:MemberNumber告诉GORM,当建立User和CreditCard之间的关系时,不是使用User模型的ID字段,而是使用User模型中的MemberNumber字段作为引用。这意味着:
CreditCard模型中的
UserNumber字段将会存储对应User模型中MemberNumber字段的值。

这样的设计使得CreditCard与User之间的关联不再依赖于用户的ID,而是依赖于用户的MemberNumber,这可能是一个业务逻辑上的需求,例如当MemberNumber是用户在系统中的一个业务标识符时。

作用和好处
这种自定义关系的好处是提供了更大的灵活性,允许开发者根据实际的业务需求和数据模型设计来建立模型间的关系。它特别有用于那些需要根据非主键字段建立关系的场景,从而可以更准确地反映实际的业务逻辑和数据关系。

多态关联

GORM为has one和has many提供了多态关联支持,他会将拥有者实体的表名、主键都保存到多态类型的字段中。

type Dog struct {
  ID   int
  Name string
  Toys []Toy `gorm:"polymorphic:Owner;"`
}

type Toy struct {
  ID        int
  Name      string
  OwnerID   int
  OwnerType string
}

db.Create(&Dog{Name: "dog1", Toys: []Toy{{Name: "toy1"}, {Name: "toy2"}}})
// INSERT INTO `dogs` (`name`) VALUES ("dog1")
// INSERT INTO `toys` (`name`,`owner_id`,`owner_type`) VALUES ("toy1","1","dogs"), ("toy2","1","dogs")

解读:
这段内容介绍了GORM如何实现和支持多态关联(Polymorphic Associations)对于"Has One"和"Has Many"关系。多态关联是一种数据库设计模式允许一个模型(在这个例子中是Toy)关联到多个模型(如Dog、Cat等),而不是只能关联到一个固定的模型。这种设计增加了数据库模型的灵活性和复用性。

示例解析:
在给定的例子中,Dog和Toy模型通过多态关系进行关联。这种关系允许Toy不仅仅属于Dog,也可以属于其他类型的实体,如Cat等,只要它们采用相同的多态设计模式。

Dog模型
Dog模型简单明了,包含ID和Name字段。此外,它包含一个Toys字段,这是一个Toy切片,表示每只狗可以拥有多个玩具。通过gorm:"polymorphic:Owner;"标签,GORM知道这是一个多态关联。

Toy模型
Toy模型包含ID、Name、OwnerID和OwnerType字段。OwnerID和OwnerType是实现多态关联的关键:
OwnerID存储拥有者的主键值。
OwnerType存储拥有者的类型,通常是拥有者实体的表名。

数据库操作示例
创建一个Dog实例并为其分配玩具时,如db.Create(&Dog{Name: “dog1”, Toys: []Toy{{Name: “toy1”}, {Name: “toy2”}}}),GORM会先在dogs表中插入一条记录,然后在toys表中插入两条记录。
在toys表中,每个玩具的owner_id会被设置为对应Dog实例的ID,而owner_type会被设置为dogs。这样就实现了多态关联:Toy知道它属于哪个表的哪条记录。

作用和好处
多态关联的主要好处是提供了极高的灵活性。 在没有多态关联的情况下,如果你想让Toy既可以属于Dog也可以属于Cat,你可能需要为每种关系创建不同的字段(比如dog_id和cat_id),或者创建不同的关联表。多态关联避免了这种冗余,允许Toy通过OwnerID和OwnerType字段灵活地关联到任意类型的实体。

总结:
这种设计模式在实际应用中非常有用,尤其是在存在多种类型的实体,并且这些实体都可能与某个资源有关联时。通过多态关联,可以简化模型设计,减少数据库复杂性,同时保持了数据结构的灵活性和扩展性。

你可以使用标签polymorphicValue来更改多态类型的值,例如:

type Dog struct {
  ID   int
  Name string
  Toys []Toy `gorm:"polymorphic:Owner;polymorphicValue:master"`
}

type Toy struct {
  ID        int
  Name      string
  OwnerID   int
  OwnerType string
}

db.Create(&Dog{Name: "dog1", Toys: []Toy{{Name: "toy1"}, {Name: "toy2"}}})
// INSERT INTO `dogs` (`name`) VALUES ("dog1")
// INSERT INTO `toys` (`name`,`owner_id`,`owner_type`) VALUES ("toy1","1","master"), ("toy2","1","master")

解读:
这段内容介绍了如何在使用GORM进行多态关联时,通过polymorphicValue标签自定义多态类型的值。在多态关联中,通常需要两个字段来确定关联的目标:一个是指向目标实体主键的外键(如OwnerID),另一个是表示目标实体类型的字段(如OwnerType)。通过polymorphicValue标签,我们可以明确指定在创建关联时,OwnerType字段应该保存的具体值,而不是使用默认的表名。

总结:使用polymorphicValue标签允许开发者在设计数据库模型时拥有更高的灵活性和控制权。它使得开发者可以根据业务逻辑需要,自定义多态关联中OwnerType字段的值,而不是简单地使用表名。

预加载

GORM 可以通过Preload预加载has many关联的记录。

自引用Has Many

type User struct {
  gorm.Model
  Name      string
  ManagerID *uint
  Team      []User `gorm:"foreignkey:ManagerID"`
}

解读:

这段内容展示了如何在使用GORM(一个Go语言的ORM库)定义自引用的"Has Many"关系。在这个例子中,User模型通过自引用来表示一个组织结构,其中一个用户可以是另一个用户的经理(或上级),并且一个经理可以管理多个下属。这种关系在数据库中通常用于表示层级或树状结构,如员工和他们的管理者。

结构体定义
ManagerID: 指向另一个User实例的指针,代表这个用户的经理。这是一个可空字段(因为它是一个指针),允许某些用户没有经理(例如,最高级别的经理)。
Team: 一个User切片,通过gorm:"foreignkey:ManagerID"标签指明,这个切片表示所有将当前用户实例作为经理的用户集合。这里使用的foreignkey:ManagerID标签告诉GORM,Team字段中的用户是通过ManagerID字段与当前用户关联的。

自引用的"Has Many"关系
在这个模型中,ManagerID字段用于标识每个用户的直接上级(经理),而Team字段则用于收集所有直接下属。通过这种方式,你可以构建一个组织中所有员工的层级关系。例如,如果一个用户(A)的ManagerID指向另一个用户(B),那么用户B的Team切片将包含用户A,表示用户A是用户B的下属。

实现细节
自引用:这个模型利用了自引用,即User结构体中包含了指向相同类型(User)的字段。这在Go中是允许的,并且在ORM中用于表示复杂的关系,如树形结构或图结构。

外键关联:通过gorm:"foreignkey:ManagerID"标签,GORM知道如何将User表中的记录通过ManagerID连接起来,建立一种层级关系。这允许GORM在执行查询时,能够自动解析这些关系,例如在查询一个用户时,同时获取他的直接下属列表。

外键约束

你可以通过constraint:OnUpdate、OnDelete实现外键约束,在使用GORM进行迁移时它会被字段创建。例如:

type User struct {
  gorm.Model
  CreditCards []CreditCard `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}

type CreditCard struct {
  gorm.Model
  Number string
  UserID uint
}

你也可以在删除记录时通过Select来删除has many关联的记录。


gorm Many To Many关系

Many To Many

在GORM中,"Many To Many"关系是指两个模型之间存在的关联,其中一个实例可以关联到多个实例,反之亦然。这种关系通常通过一个关联表(或称为连接表)来实现,该表存储两个模型之间关联的外键

示例:用户和角色
假设我们有两个模型:User和Role。一个用户可以有多个角色,同时一个角色也可以被多个用户共享。这是一个典型的"Many To Many"关系。

定义模型
首先,我们定义User和Role模型,并使用GORM的标签来描述它们之间的"Many To Many"关系。

package main

import (
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
)

type User struct {
    gorm.Model
    Name  string
    Roles []Role `gorm:"many2many:user_roles;"`
}

type Role struct {
    gorm.Model
    Name  string
    Users []User `gorm:"many2many:user_roles;"`
}

在这个例子中,User和Role模型通过Roles和Users字段相互关联,而gorm:"many2many:user_roles;"标签指定了用于存储这种多对多关系的关联表名称为user_roles。

自动迁移
GORM提供自动迁移功能,可以根据模型定义自动创建或更新数据库表结构。

func main() {
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    if err != nil {
        panic("failed to connect database")
    }

    // 自动迁移
    db.AutoMigrate(&User{}, &Role{})
}

创建关联
接下来,我们可以创建一些User和Role实例,并设置它们之间的关联。

func main() {
    // ...数据库连接和自动迁移代码

    // 创建角色
    adminRole := Role{Name: "Admin"}
    userRole := Role{Name: "User"}
    db.Create(&adminRole)
    db.Create(&userRole)

    // 创建用户并分配角色
    user := User{Name: "John", Roles: []Role{adminRole, userRole}}
    db.Create(&user)
}

在上面的代码中,我们首先创建了两个Role实例:Admin和User。然后,我们创建了一个User实例John,并通过Roles字段将John与这两个角色关联起来。当我们执行db.Create(&user)时,GORM会自动处理这些关联,不仅在users和roles表中插入相应的记录,还会在user_roles关联表中插入表示这些关联的记录。

小结
通过GORM,我们可以相对简单地实现和管理"Many To Many"关系。GORM自动处理关联表的创建和更新,开发者只需专注于模型的定义和业务逻辑。这种抽象使得处理复杂的数据库关联变得更加直观和高效。

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的AutoMigrate为User创建表时,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;"`
}

解读:
这段内容展示了如何在GORM中实现和使用**"Many To Many"关系的反向引用。**在这个示例中,有两个模型:User和Language。一个用户可以会说多种语言,同时一种语言也可以被多个用户所会说。这种关系通过一个名为user_languages的连接表来表示和存储。连接表是在数据库中用来存储两个表之间"多对多"关系的第三个表,它通常包含指向两个表主键的外键。

User模型
User模型包含一个Languages字段,这是一个指向Language结构的指针切片。这表明一个User可以关联多个Language。
gorm:"many2many:user_languages;"标签指定了用来存储User和Language之间关系的连接表名称为user_languages。这个标签告诉GORM如何管理这两个模型之间的多对多关系。

Language模型
Language模型类似地定义了一个Users字段,这是一个指向User结构的指针切片。这表示一个Language可以被多个User所使用。
它同样使用了gorm:"many2many:user_languages;"标签来指定连接表。这样,GORM就知道如何通过user_languages表反向管理Language到User的多对多关系。

连接表(user_languages)
连接表user_languages在数据库中通常会有两个主要的列:一个是user_id列,用来存储User表中某个用户的ID;另一个是language_id列,用来存储Language表中某种语言的ID。每一行代表一个用户与一种语言之间的关系。

反向引用的作用
双向查询这种反向引用的设置允许你从两个方向查询关系。即你可以轻松地找到一个用户会说的所有语言,也可以找到说某种语言的所有用户。
数据完整性:通过管理user_languages连接表,GORM能够确保在添加、更新或删除用户和语言时,保持数据的一致性和完整性。
灵活性:这种模型设计提供了很高的灵活性,适用于需要双向多对多关系的场景,比如用户和群组、商品和分类等。

总结,通过使用GORM的many2many标签和指定连接表,你可以方便地在两个模型之间建立和管理复杂的多对多关系,同时保持代码的清晰和数据库的整洁。

重写外键

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

type User struct {
  gorm.Model
  Languages []Language `gorm:"many2many:user_languages;"`
}

type Language struct {
  gorm.Model
  Name string
}

// 连接表:user_languages
//   foreign key: user_id, reference: users.id
//   foreign key: language_id, reference: languages.id

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

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"`
}

// 会创建连接表:user_profiles
//   foreign key: user_refer_id, reference: users.refer
//   foreign key: profile_refer, reference: profiles.user_refer

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

解读:
在GORM中,通过使用many2many标签,你可以定义两个模型之间的多对多关系,并且默认情况下,GORM会自动创建一个连接表来管理这种关系。连接表包含两个外键,分别指向参与关系的两个模型的主键。然而,有时候默认的外键和引用规则可能不符合你的数据库设计需求,这时候你可以使用foreignKey、references、joinForeignKey、和joinReferences标签来重写这些规则。

标签解释
foreignKey: 指定本模型在连接表中使用的外键字段。
references: 指定foreignKey指向本模型中的哪个字段。
joinForeignKey: 指定另一模型在连接表中使用的外键字段。
joinReferences: 指定joinForeignKey指向另一模型中的哪个字段。

示例解读
在提供的示例中,User和Profile模型通过一个名为user_profiles的连接表建立多对多关系。与前面的User和Language模型的默认外键规则不同,这里通过标签明确指定了连接表中使用的外键名称和它们所引用的字段。

User模型
foreignKey:Refer:指定User模型在user_profiles连接表中使用的外键字段应该基于User模型的Refer字段。
joinForeignKey:UserReferID:指定连接表中代表User模型的外键字段名称为UserReferID。
References:UserRefer:这似乎是一个笔误或误解。基于上下文,它应该是用来指定foreignKey引用User模型中的哪个字段,但Refer已被作为foreignKey,所以这里可能是要表达foreignKey对应的实际字段是Refer。

Profile模型
joinReferences:ProfileRefer:指定joinForeignKey在Profile模型中引用的字段。但示例中没有直接展示joinForeignKey的定义,从上下文推测,joinForeignKey可能是通过gorm:"many2many:user_profiles;"在User模型中定义的joinForeignKey:UserReferID对应的另一边,意味着ProfileRefer应该是user_profiles表中的列名,指向Profile模型。

连接表user_profiles
foreign key: user_refer_id, reference: users.refer:表示user_profiles表中的user_refer_id列是外键,它引用users表中的refer列。
foreign key: profile_refer, reference: profiles.user_refer:表示user_profiles表中的profile_refer列是外键,它引用profiles表中的user_refer列。

注意事项
唯一索引(Unique Index):某些数据库要求只能在具有唯一索引的字段上创建外键。这就意味着,如果你打算在迁移时创建外键,那么被引用的字段(如Refer和UserRefer)需要被标记为唯一索引,这在GORM中可以通过gorm:"index:,unique"标签来实现。

自引用 Many2Many

type User struct {
  gorm.Model
    Friends []*User `gorm:"many2many:user_friends"`
}

// 会创建连接表:user_friends
//   foreign key: user_id, reference: users.id
//   foreign key: friend_id, reference: users.id

解读:
这段内容说明了如何在使用GORM时定义一个自引用的"Many To Many"关系,具体示例为用户与其朋友之间的关系。在这个例子中,User模型通过一个名为user_friends的连接表来实现用户之间的多对多朋友关系。这意味着每个用户可以有多个朋友,而每个朋友也可以同时被多个用户标记为朋友。

User模型
User模型包含了一个Friends字段,这是一个指向User类型的切片。这个字段使用gorm:"many2many:user_friends"标签来声明一个多对多关系,并指定user_friends作为连接表。

连接表user_friends
连接表user_friends用于存储用户之间朋友关系的信息。它包含两个外键字段:user_id和friend_id。
user_id:作为外键,指向users表的id字段,表示在这个朋友关系中的一个用户。
friend_id:同样作为外键,也指向users表的id字段,但表示在这个关系中的另一个用户,即朋友。
这个设计允许每条记录在user_friends表中唯一地标识一对朋友关系,其中user_id和friend_id分别代表这对关系中的两个用户。值得注意的是,由于这是自引用关系,user_id和friend_id都引用同一个表(users表)的id字段。

自引用的"Many To Many"关系的特点
对称性:在现实世界中,如果用户A是用户B的朋友,那么用户B通常也是用户A的朋友。然而,在数据库层面,这种对称性需要通过在user_friends表中为每对朋友关系添加两条记录来手动维护,除非应用逻辑层提供了处理这一点的机制。
灵活性:这种模型设计极大地增加了数据库模型的灵活性,允许用户动态地添加或删除朋友关系。
查询:查询一个用户的所有朋友涉及到连接表的自连接查询,这可能比直接的"一对多"或"一对一"关系更复杂一些。

预加载

GORM可以通过Preload预加载has many关联的记录。

自定义连接表

连接表可以说一个全功能的模型,支持soft Delete、钩子、更多字段,就根其他模型一样。您可以通过SetupJoinTable指定它,例如:
注意:自定义连接表要求外键是复合主键或复合唯一索引

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 `gorm:"primaryKey"`
  AddressID int `gorm:"primaryKey"`
  CreatedAt time.Time
  DeletedAt gorm.DeletedAt
}

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

// 修改 Person 的 Addresses 字段的连接表为 PersonAddress
// PersonAddress 必须定义好所需的外键,否则会报错
err := db.SetupJoinTable(&Person{}, "Addresses", &PersonAddress{})

解读:
这段内容解释了在GORM中如何自定义"Many To Many"关系的连接表,并且如何为这个连接表添加额外的字段和功能,比如软删除(Soft Delete)和钩子(Hooks)。通过这种方式,连接表不仅仅是用于存储两个模型之间关系的简单表,而是可以成为一个全功能的模型,类似于其他的GORM模型。

自定义连接表的模型定义
Person模型
Person模型定义了一个Addresses字段,通过gorm:"many2many:person_addresses;"标签指明和Address模型之间的多对多关系,并且指定使用person_addresses作为连接表。

Address模型
Address模型是简单的,包含ID和Name字段。

PersonAddress连接表模型
PersonAddress是自定义的连接表模型,除了包含表示关系的PersonIDAddressID作为复合主键之外,还添加了CreatedAt和DeletedAt字段。DeletedAt字段支持GORM的软删除功能。
PersonAddress模型也可以定义方法如BeforeCreate钩子,这在GORM中用于在创建记录之前自动执行特定逻辑。

自定义连接表的设置
通过db.SetupJoinTable(&Person{}, “Addresses”, &PersonAddress{})调用,GORM被指示使用PersonAddress作为Person和Address之间关系的连接表。这允许开发者利用GORM的高级功能,比如钩子和软删除,在连接表上添加更多的字段。

注意事项
外键要求:自定义连接表要求外键是复合主键或复合唯一索引。这意味着在PersonAddress模型中,PersonID和AddressID需要被标记为primaryKey,以确保每个关系在表中是唯一的。

软删除:通过在连接表模型中添加DeletedAt字段,可以为连接表记录实现软删除功能。这意味着,删除操作会更新DeletedAt字段而不是从数据库中物理删除记录,允许你保留和查询被标记为删除的关系。

钩子:如BeforeCreate,允许你在创建连接表记录之前执行自定义逻辑,这与其他GORM模型的行为一致。

总结:
通过自定义连接表,GORM提供了极大的灵活性和控制力,使得开发者可以更细致地管理模型之间的多对多关系,同时利用GORM提供的各种特性来增强连接表的功能。

外键约束

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

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

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

复合外键

如果你的模型使用了复合主键,GORM会默认启用复合外键。
你也可以覆盖默认的外键,指定多个外键,只需要逗号分隔那些键名,例如:

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"`
}

// 连接表: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

// 连接表:locale_blog_tags
//   foreign key: blog_id, reference: blogs.id
//   foreign key: blog_locale, reference: blogs.locale
//   foreign key: tag_id, reference: tags.id

// 连接表:shared_blog_tags
//   foreign key: blog_id, reference: blogs.id
//   foreign key: tag_id, reference: tags.id

解读:
这段内容解释了在使用GORM处理复合主键时如何定义和自定义复合外键关系。复合主键是指使用两个或更多的列来唯一标识表中的每行记录。当模型使用复合主键时,GORM默认启用复合外键来匹配这些复合主键。然而,GORM也提供了灵活性来覆盖这些默认行为,允许指定自定义的外键组合。

复合主键与复合外键
在提供的例子中,Tag和Blog模型都使用了复合主键(ID和Locale),这意味着每个Tag和Blog的唯一性不仅仅由ID决定,还需要Locale来共同确定。

自定义外键关系
Blog模型的Tags字段
默认行为:对于Tags字段,GORM默认使用模型的复合主键(ID和Locale)作为复合外键。这意味着在blog_tags连接表中,会有四个外键blog_id和blog_locale用来引用Blog模型,tag_id和tag_locale用来引用Tag模型。

Blog模型的LocaleTags字段
自定义外键:通过ForeignKey:id,locale;References:id标签,明确指定了使用Blog的ID和Locale作为外键,并且指定它们引用Tag模型的ID字段。这里似乎有一个小错误或遗漏,因为References应该指向Tag模型的ID和Locale字段来正确映射复合外键。理论上,正确的标签可能是ForeignKey:id,locale;References:id,locale。

Blog模型的SharedTags字段
简化的外键关系:SharedTags字段示例通过ForeignKey:id;References:id标签,只使用ID字段作为外键和引用。这在shared_blog_tags连接表中创建了一个简化的关系,只包括blog_id和tag_id,没有考虑Locale,这可能用于那些Locale不是区分共享标签重要因素的场景。

连接表的结构
blog_tags:这个连接表包含了所有四个外键,完整地表示了Blog和Tag之间复合主键的多对多关系。
locale_blog_tags:这个连接表应该包含了Blog和Tag之间特定于语言的关联,但示例中的标签定义可能有误,理应包含Locale的映射。
shared_blog_tags:仅基于ID的简化多对多关系,适用于跨语言共享的标签。

总结
通过这种方法,GORM提供了灵活的方式来定义和自定义复合外键,以及如何在多对多关系中使用它们。这使得开发者可以根据具体需求精确控制数据库关系的结构,尤其是在涉及复合主键时。然而,在自定义外键和引用时需要小心确保标签的正确性和一致性,以避免错误和混淆。

02-15 13:44