gorm day7
- gorm Belongs To关系
- gorm Has One关系
gorm Belongs To关系
在看文档之前先进行一些基本概念的学习:
什么是主键?(Primary Key)
主键是一个表中的特定列(或一组列),用来唯一标识表中的每一行记录。一个表只能有一个主键。
主键的值必须是唯一的,不允许为空(NULL)。
主键通常是表的第一个列,但也可以设置为多列的组合,这种情况称为复合主键。
什么是外键?(Foreign Key)
外键是一个表中的列,**它用来链接另一个表的主键,**建立两个表之间的关系。
外键的值必须匹配另一个表的主键列,或者是 NULL。如果是 NULL,则表示没有关联的记录。
通过外键,数据库可以维护表之间的数据完整性和关系约束。
例子:
假设有两个表:Authors 和 Books。每位作者(Author)可以写多本书(Books),但每本书只能有一个作者。这里,Authors 表的 ID 字段是主键,Books 表的 AuthorID 字段是外键。
CREATE TABLE Authors (
ID INT PRIMARY KEY,
Name VARCHAR(100)
);
CREATE TABLE Books (
ID INT PRIMARY KEY,
Title VARCHAR(100),
AuthorID INT,
FOREIGN KEY (AuthorID) REFERENCES Authors(ID)
);
Authors 表:
ID 是主键,用于唯一标识每位作者。
Name 是作者的名字。
Books 表:
ID 是主键,用于唯一标识每本书。
Title 是书的标题。
AuthorID 是外键,它引用 Authors 表的 ID 字段。这表示每本书都关联到一个作者。
关系解释
通过在 Books 表中设置 AuthorID 作为外键,我们建立了 Books 和 Authors 之间的一对多关系:即一个作者可以对应多本书,但每本书只能对应一个作者。
如果尝试在 Books 表中插入一个 AuthorID 不存在于 Authors 表中的值,数据库将拒绝这种插入操作,因为这违反了外键约束。
通过使用主键和外键,数据库不仅能够有效地组织和关联数据,还能通过这些约束来保证数据的一致性和完整性。
GORM中的一对一关系
在 GORM 中,“Belongs To” 关系表示两个模型之间的一对一关系,其中一个模型拥有另一个模型的外键。这种关系通常用于表示拥有者(owner)和被拥有(owned) 实体之间的关系。
先学一个简单的例子再看文档:
举例说明
假设我们有两个模型:User 和 Profile,其中每个 User 都有一个与之关联的 Profile。在这种情况下,Profile 属于 User,因为 Profile 包含指向 User 的外键。
package main
import (
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"log"
)
type User struct {
gorm.Model
Name string
Profile Profile
}
type Profile struct {
gorm.Model
UserID uint // 这是外键
Bio string
}
func main() {
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
log.Fatal("failed to connect database")
}
// 自动迁移 schema
db.AutoMigrate(&User{}, &Profile{})
// 创建 User 和关联的 Profile
user := User{Name: "John Doe", Profile: Profile{Bio: "Gopher"}}
db.Create(&user)
// 查询 User 并加载其 Profile
var queriedUser User
db.Preload("Profile").Find(&queriedUser, user.ID)
log.Println(queriedUser.Name, queriedUser.Profile.Bio)
}
代码解读:
模型定义:User 模型有一个 Profile 字段,表示每个 User 都关联一个 Profile。 Profile 模型中的 UserID 字段作为外键,指向 User 模型的 ID,表明每个 Profile 属于一个 User。
自动迁移(AutoMigrate):GORM 的 AutoMigrate 方法用来根据模型定义自动创建或更新数据库表。在这个例子中,它会创建 users 和 profiles 表,并确保 profiles 表有一个指向 users 表的外键 user_id。
创建记录:通过 db.Create(&user) 创建一个 User 实例和与之关联的 Profile 实例。GORM 会自动处理外键关系,并在数据库中正确插入记录。
查询并加载关联:db.Preload(“Profile”).Find(&queriedUser, user.ID) 查询指定的 User 并预加载(Preload)其关联的 Profile。Preload 使得在查询 User 的同时,也自动加载其 Profile,避免了后续单独查询 Profile 的需要。
这里我再解读什么是预加载?
预加载(Preloading)是 ORM(对象关系映射)框架中的一个概念,用于自动加载数据库中的关联记录。当你查询某个实体时,预加载可以同时加载与之相关联的其他实体或集合。这通常通过执行额外的查询来实现,然后将结果自动填充到主查询对象的关联属性中。预加载主要用于解决“N+1 查询问题”,提高数据查询效率。
什么时候需要使用预加载?
当你需要访问对象及其相关联对象的数据时,使用预加载可以一次性加载所有需要的数据,避免了多次查询数据库,从而减少了数据库的访问次数和提高了应用性能。特别是在以下情况下特别有用:
展示关联数据:当你需要在用户界面上展示对象及其关联数据时,如展示每本书及其作者信息。
避免 N+1 查询问题:在没有预加载的情况下,如果你先查询主对象列表,然后对于列表中的每个对象再去查询关联对象,这将导致大量的数据库查询,即 N+1 查询问题。
预加载示例:
假设我们有两个模型:Author 和 Book,每个 Author 可以有多本 Book。我们希望查询某个作者及其所有书籍。
package main
import (
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"log"
)
type Author struct {
gorm.Model
Name string
Books []Book
}
type Book struct {
gorm.Model
Title string
AuthorID uint
}
func main() {
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
log.Fatal("failed to connect database")
}
// 自动迁移 schema
db.AutoMigrate(&Author{}, &Book{})
// 创建一个 Author 及其 Books
author := Author{Name: "John Doe", Books: []Book{{Title: "GORM Guide"}, {Title: "GORM Tips"}}}
db.Create(&author)
// 查询 Author 并预加载其 Books
var queriedAuthor Author
db.Preload("Books").First(&queriedAuthor, author.ID)
log.Printf("Author: %v, Books: %v", queriedAuthor.Name, queriedAuthor.Books)
}
在上述代码中,我们首先创建了一个 Author 记录以及关联的 Book 记录。
使用 db.Preload(“Books”).First(&queriedAuthor, author.ID) 查询时,GORM 不仅会加载请求的 Author,还会自动加载与该 Author 关联的所有 Book 记录。
这意味着,当我们访问 queriedAuthor.Books 时,相关的书籍数据已经被加载好了,无需再执行额外的数据库查询。
Belongs To关系总结:
“Belongs To” 关系是 GORM 中表示一对一关联的一种方式,它允许你在相关模型之间定义清晰的所有权关系。通过声明外键和使用 GORM 提供的方法(如 Preload),可以轻松管理和查询这些关联的数据。
一句话总结Belongs to咋实现的:上面这个例子:User就是直接包含要拥有的结构体,被拥有的就通过UserID uint字段来建立关系。
这里来具体说说怎么实现:
在 GORM 中,遵循 表名 + ID 命名约定来指定外键字段名称是一种常见做法,但它不是强制性的。这种约定有助于清晰地表示外键字段是如何与另一张表的主键相关联的。然而,GORM 提供了灵活的方式来自定义外键名称,如果你不遵循这个约定,你可以使用 GORM 的标签来明确指定外键和引用字段。
外键命名约定
默认约定:对于一对一和一对多的关系,如果你不显式指定外键,GORM 会使用模型名称加上 ID 的形式来寻找外键。例如,如果 Profile 模型属于 User 模型,GORM 默认会期望在 Profile 中找到一个名为 UserID 的字段作为外键。
自定义外键:如果你的外键名称不遵循默认的命名约定,或者你想使用不同的字段作为外键,你可以使用 GORM 的 foreignKey 标签在模型定义中明确指定。例如:
type Profile struct {
gorm.Model
UserRefer uint // 自定义外键字段名
Bio string
}
type User struct {
gorm.Model
Name string
Profile Profile `gorm:"foreignKey:UserRefer"` // 明确指定外键为 UserRefer
}
看好在拥有者那里声明被拥有者的外键。
对于外键字段,使用 uint(无符号整型)是很常见的,因为它对应于许多数据库自动生成的主键的数据类型(如自增ID)。无符号整型足以容纳大多数情况下的主键值,并且避免了负数ID的可能性,这在实际应用中很少见。
然而,外键的具体数据类型应该与它引用的主键字段的数据类型相匹配。如果主键字段是 int 或其他类型,外键字段也应该使用相同的类型以保证数据的一致性和正确的关联。
比如type Profile struct {
gorm.Model
UserRefer uint // 自定义外键字段名
Bio string
}它的主键就在gorm.Model中,是ID,而且是uint类型的。
但是我觉得分析包含关系不能太公式化,因为有的例子不这样。比如下面的文档例子。
gorm文档的Belongs To
现在看懂这个文档简直太简单了:
belongs to会与另一个模型建立了一对一的连接。这种模型的每一个实例都属于另一个模型的实例。
例如,您的应用包含user和company,并且每个user能且智能被分配给一个conpany。下面的类型就表示这种关系,注意,在User对象中,有一个和company一样的companyID。默认情况下,CompanyID被隐含的用来在User和Company之间创建一个外键关系,因此必须包含在User结构体中才能填充Company内部结构体。
// `User` 属于 `Company`,`CompanyID` 是外键
type User struct {
gorm.Model
Name string
CompanyID int
Company Company
}
type Company struct {
ID int
Name string
}
例子解读:
这个看起来感觉关系像单向的,这里进行解读,这是一个很典型的Belongs To关系。
type User struct {
gorm.Model
Name string
CompanyID int
Company Company
}
其实这里是一种变通,这里为什么CompanyID是外键,还是表明+ID的原理。
关系定义
User属于Company:通过在User结构体中包含一个Company类型的字段,我们表明了每个User都关联到一个Company,CompanyID字段作为外键,存储了对应Company记录的ID值。
如何建立关系
外键:在 User 结构体中,CompanyID 字段明确指出了 User 和 Company 之间的关联关系。GORM 通过这个字段知道如何在数据库中查找对应的 Company 记录。CompanyID 的值是 Company 表中某条记录的 ID 值。
没有直接的 UserID 在 Company 中:在 “Belongs To” 关系中,并不需要在 “拥有者”(Company)中定义一个指向 “被拥有者”(User)的字段(如 UserID)。这是因为关系的定义是单向的,从 User 指向 Company。如果你想从 Company 访问其所有 User,那是另一种关系类型—“Has Many”。
关系的单向性和双向性
单向性:在这个例子中,关系是单向定义的,从 User 到 Company。这意味着你可以很容易地从一个 User 记录找到它所属的 Company 记录,但是从 Company 记录出发并不直接知道哪些 User 属于它。
实现双向访问:如果你想在 Company 模型中也能访问其所有 User 记录,你可以在 Company 结构体中添加一个 User 切片,并使用 GORM 的标签来指定关系:
type Company struct {
ID int
Name string
Users []User `gorm:"foreignKey:CompanyID"` // 指定 Company 到 User 的关系
}
通过这种方式,Company 模型现在包含了一个 Users 字段,GORM 会自动处理这个 “Has Many” 关系,允许你从 Company 实体访问所有相关的 User 实体。
总结
我之前觉得怪怪的就是没有意识到关系的单向性和双向性的问题。
在 “Belongs To” 关系中,不需要在 “拥有者”(Company)中定义一个指向 “被拥有者”(User)的字段,因为关系是通过 User 中的外键字段来定义的。
如果需要实现从 Company 访问 User 的双向访问,可以在 Company 中添加一个指向多个 User 的切片,并适当配置 GORM 标签来表示这种 “Has Many” 关系。
重写外键
要定义一个belongs to关系,数据库的表中必须存在外键。默认情况下,使用者的类型名称加上表的主键的字段名字,例如,定义一个User实体属于Company实体,那么外键的名字一般使用CompanyID。
Gorm同时提供自定义外键,如下例所示:
type User struct {
gorm.Model
Name string
CompanyRefer int
Company Company `gorm:"foreignKey:CompanyRefer"`
// 使用 CompanyRefer 作为外键
}
type Company struct {
ID int
Name string
}
Company Company gorm:"foreignKey:CompanyRefer"
这个就像是一个显示声明,在 Company 字段的 GORM 标签中,通过 foreignKey:CompanyRefer 明确指定了 CompanyRefer 字段作为 User 与 Company 之间关联的外键。这告诉 GORM,当处理 User 和 Company 之间的关联时,应该使用 CompanyRefer 字段作为外键。
效果
这种方式提供了额外的灵活性,允许你根据实际的数据库设计或个人偏好来命名外键。
当你查询 User 实体并希望连同其所属的 Company 实体一起加载时,GORM 会使用 CompanyRefer 字段来解析这个关系,并正确地加载关联的 Company 实体。
重写引用:
对于belongs to 关系,GORM通常使用数据库表,主表(拥有者)的主键值作为外键参考,正如上面的例子,我们使用主表Company中的主键字段ID作为外键的参考值。
如果在Company实体中设置了User实体,那么GORM会自动把Company中的ID属性保存到User的Company属性中。
通用的,您也可以使用标签references来更改它,例如:
type User struct {
gorm.Model
Name string
CompanyID string
Company Company `gorm:"references:Code"` // 使用 Code 作为引用
}
type Company struct {
ID int
Code string
Name string
}
解读:
这段内容描述了在 GORM 中如何自定义 “Belongs To” 关系中外键引用的字段。默认情况下,GORM 使用被关联模型(拥有者,如 Company)的主键(通常是 ID 字段)作为外键的参考值。然而,你可以通过 references 标签指定一个不同的字段作为外键的参考。
重写引用的解释
默认引用:在没有使用 references 标签的情况下,GORM 会假定外键 (CompanyID 在 User 结构体中) 引用了关联模型 (Company) 的主键 (ID)。这意味着,CompanyID 字段将存储对应 Company 记录的 ID 值。
自定义引用:通过 references 标签,你可以指定一个不同的字段作为关联的依据。在你提供的例子中,Company 的 Code 字段被用作外键引用,而不是默认的 ID 字段。
示例解释:
type User struct {
gorm.Model
Name string
CompanyID string // 注意这里 CompanyID 的类型是 string
Company Company `gorm:"references:Code"` // 使用 Company 的 Code 字段作为外键引用
}
type Company struct {
ID int
Code string // 自定义的唯一标识符,用作 User 的外键引用
Name string
}
在这个例子中,User 和 Company 之间的关联不再是通过 Company 的 ID 字段,而是通过 Company 的 Code 字段来建立的。这意味着 User 表中的 CompanyID 字段将存储与其关联的 Company 记录的 Code 值。
可能对于上面举得这个例子你有点疑惑,这里我进一步解读:
通过使用 references:Code 标签,我们告诉 GORM 在处理 User 和 Company 之间的关系时,User 表中的 CompanyID 字段应该存储 Company 表中某条记录的 Code 值,而不是通常的 ID 值。这里的 Code 值是指 Company 表中的 Code 字段,它被用作连接两个表的参考值。
通常,外键字段(在这个例子中是 User 的 CompanyID)存储的是它所引用的表的主键值。但是,在这个例子中,我们改变了这个行为,让 CompanyID 存储的是 Company 记录的 Code 字段的值。
重点澄清
**正常情况:**没有使用 references 标签时,User 的 CompanyID 字段会存储一个 Company 的 ID 值,作为外键连接到 Company 表。
**自定义引用:**通过在 User 结构体中对 Company 字段使用 gorm:“references:Code”,你告诉 GORM,User 的 CompanyID 字段应该存储对应的 Company 记录的 Code 字段的值,而非 ID 字段的值。这意味着,当你创建或查询 User 记录时,GORM 会使用 Company 的 Code 字段值来建立关联,而不是使用 ID。
这种方式在 Company 的 Code 字段是唯一的情况下非常有用,特别是当你想要通过一个非主键的唯一标识符来建立模型之间的关联时。
Belongs to 的CRUD
点击关联模式链接获取belongs to相关的用法
预加载
这里刚刚已经说过了,这里再补充:
GORM允许通过使用Preload或者Joins来主动加载实体的关联关系。
外键约束
你可以通过OnUpdate,OnDelete配置标签来增加关联关系的级联操作,如下面的例子,通过GORM可以完成用户和公司的级联更细和级联操作:
type User struct {
gorm.Model
Name string
CompanyID int
Company Company `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}
type Company struct {
ID int
Name string
}
这段内容讲述了如何在 GORM 中**使用外键约束来定义关联关系中的级联操作。**在关系型数据库中,级联操作是指当更新或删除一个表中的记录时,自动对相关联的表中的记录执行特定的操作。这在管理具有外键关系的数据时非常有用,因为它帮助维持数据库的引用完整性。
解析例子
例子中,User 和 Company 之间存在一个关联关系,其中 User 表通过 CompanyID 字段关联到 Company 表。
type User struct {
gorm.Model
Name string
CompanyID int
Company Company `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}
type Company struct {
ID int
Name string
}
OnUpdate:CASCADE:这个配置表示如果 Company 表中的某条记录被更新(例如,其 ID 发生变化),那么所有相关联的 User 记录的 CompanyID 字段也会相应地被更新。“CASCADE” 操作确保了数据的一致性,即使在更新操作发生时。
OnDelete:SET NULL:这个配置表示如果 Company 表中的某条记录被删除,那么所有相关联的 User 记录中的 CompanyID 字段将被设置为 NULL。这样做的目的是在删除操作发生时,避免产生悬浮引用(即指向不存在的记录的外键),同时也保留了 User 记录的其他信息。
为什么需要级联操作
级联操作的使用场景包括但不限于:
数据一致性:确保数据库中的数据保持一致性,避免因为关联数据的变化而造成数据不一致的问题。
简化数据操作:自动化处理关联数据的更新和删除操作,减少手动编写额外逻辑的需要。
使用级联操作的注意事项
在使用级联删除(OnDelete:CASCADE) 时要特别小心,因为它会导致删除一条记录时连带删除所有相关联的记录,这可能不总是预期的行为。
确保数据库支持你要使用的级联操作。虽然大多数现代关系型数据库都支持这些操作,但具体实现和支持的详细程度可能会有所不同。
在实现级联操作之前,仔细考虑业务逻辑和数据完整性的需求,以选择最适合的策略。
总结:
通过在模型定义中使用 GORM 的 constraint 标签和相应的配置,gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"
,你可以灵活地控制数据库外键约束的行为,从而实现复杂的数据维护逻辑。
gorm Has One关系
Has One
has one与另一个模型建立一对一的关联,但它和一对一关系又些许不同。这种关联表明一个模型的每个实例都包含或拥有另一个模型的一个实例。
例如,你的应用包含user和credit card模型,且每个user只能有一张credit card。
// User 有一张 CreditCard,UserID 是外键
type User struct {
gorm.Model
CreditCard CreditCard
}
type CreditCard struct {
gorm.Model
Number string
UserID uint
}
解读:
这段内容描述了在 GORM 中如何使用 “Has One” 关系来表示模型之间的一对一关联。这种关系表明一个模型(在这个例子中是 User)包含或拥有另一个模型(CreditCard)的单个实例。与 “Belongs To” 关系相比,“Has One” 关系的方向性略有不同,强调的是拥有关系,即一个实体拥有另一个实体。
“Has One” 关系的特点
拥有方与被拥有方:在 “Has One” 关系中,通常有一个拥有方(拥有另一个模型实例的模型)和一个被拥有方(被另一个模型实例拥有的模型)。
外键的位置:与 “Belongs To” 关系不同,“Has One” 关系中的外键位于被拥有方(在这个例子中是 CreditCard)。
单一实例:每个拥有方的实例最多只能拥有一个被拥有方的实例。
示例解析:
type User struct {
gorm.Model
CreditCard CreditCard // User 模型有一个 CreditCard 字段,表示 User 拥有一张 CreditCard
}
type CreditCard struct {
gorm.Model
Number string // CreditCard 的具体信息,如卡号
UserID uint // 外键,指向 User 模型的 ID,表示这张 CreditCard 属于哪个 User
}
User 模型:代表用户,它拥有一个 CreditCard 字段,表示每个用户最多拥有一张信用卡。
CreditCard 模型:代表信用卡,包含一个 UserID 字段,这是一个外键,用于指向拥有该信用卡的用户。
数据库设计
在数据库层面,CreditCard 表将包含一个 UserID 列,存储与之关联的 User 记录的 ID 值。这表明每张信用卡都是由一个特定的用户拥有。
GORM 处理
当使用 GORM 自动迁移功能时,GORM 会基于这些模型定义来创建相应的表,并设置正确的外键约束。
当查询 User 时,可以使用 GORM 的 Preload 功能来自动加载关联的 CreditCard,从而一次性获取用户和他们的信用卡信息。
总结:
“Has One” 关系在 GORM 中用于表示两个模型之间的一对一拥有关系,其中外键位于被拥有方。这种关系允许模型清晰地表示它们之间的拥有关系,同时通过外键约束维护数据库的引用完整性。
重写引用
默认情况下,拥有者实体会将has one对于模型的主键保存为外键,您也可以修改它,用另外一个字段来保存,例如下以恶个这个实验Name来保存的例子。
您可以实验标签references来更改它,例如:
type User struct {
gorm.Model
Name string `gorm:"index"`
CreditCard CreditCard `gorm:"foreignkey:UserName;references:name"`
}
type CreditCard struct {
gorm.Model
Number string
UserName string
}
解读:
这段内容解释了在 GORM 中如何自定义 “Has One” 关系中的外键和引用字段。在默认情况下,“Has One” 关系通过将被拥有方(例如 CreditCard)的外键字段设置为引用拥有方(例如 User)的主键(通常是 ID)来建立。然而,GORM 允许你通过使用 foreignkey 和 references 标签来自定义这些字段,即指定一个非主键字段作为关联的依据。
示例解析
User 模型 拥有一个 CreditCard。不同于通常使用 User 的 ID 作为外键的做法,这里使用 User 的 Name 字段作为外键的引用。
CreditCard 模型 包含一个 UserName 字段,通过 gorm:“foreignkey:UserName;references:name” 标签指定这个字段作为外键,它引用了 User 模型的 Name 字段。
外键与引用的自定义
foreignkey:UserName:这部分告诉 GORM,CreditCard 中的 UserName 字段应该用作外键。
references:name:这部分指定 CreditCard 的外键 UserName 应该引用 User 模型中的 Name 字段,而不是默认的 ID 字段。
引用解读,我当时看到引用这个概念我觉得有点难懂
这里举一个具体的例子来进行解释这个概念:
假设我们有两个表:users 和 credit_cards。在常规的数据库设计中,credit_cards 表可能通过 user_id 外键引用 users 表的主键 id 字段,以建立两者之间的关系。但是,在我们的特殊情况下,我们希望通过 users 表的 name 字段来建立这种关系,而不是通过 id 字段。
users 表:
id name
1 Alice
2 Bob
credit_cards 表:
id number user_name
1 1234-5678-9012 Alice
2 9876-5432-1098 Bob
关系说明
每个 User 可以拥有一张 CreditCard。
CreditCard 表中的 user_name 字段用来存储与之关联的 User 的 name 值。
GORM 模型定义
type User struct {
gorm.Model
Name string
CreditCard CreditCard
}
type CreditCard struct {
gorm.Model
Number string
UserName string `gorm:"index;"`
}
并且,我们通过 gorm:“foreignkey:UserName;references:name” 在 User 模型中指定 CreditCard 的外键为 UserName 字段,这个字段引用 User 模型中的 Name 字段。
实际数据库操作
当你在 GORM 中创建一个 User 并分配给他一张 CreditCard 时,GORM 会把 CreditCard 的 UserName 字段设置为对应 User 的 Name 值。这意味着,credit_cards 表中的 user_name 字段直接引用了 users 表的 name 字段的值,而不是通常的主键 id 值。
重点:
在这种设计中,name 字段在 users 表中需要是唯一的,因为它被用作连接两个表的参考值。
这种方法允许你根据业务逻辑或数据模型的特殊要求,通过非主键字段建立表之间的关系。
使用 references 标签可以让你灵活地指定哪个字段应该被用作关系的参考,这在处理复杂或非标准的数据库设计时非常有用。
多态关联
GORM为has one和has many提供了多态关联支持,它会将拥有者实体的表名,主键值都保存到多态类型的字段中。
type Cat struct {
ID int
Name string
Toy Toy `gorm:"polymorphic:Owner;"`
}
type Dog struct {
ID int
Name string
Toy Toy `gorm:"polymorphic:Owner;"`
}
type Toy struct {
ID int
Name string
OwnerID int
OwnerType string
}
db.Create(&Dog{Name: "dog1", Toy: Toy{Name: "toy1"}})
// INSERT INTO `dogs` (`name`) VALUES ("dog1")
// INSERT INTO `toys` (`name`,`owner_id`,`owner_type`) VALUES ("toy1","1","dogs")
解读:
gorm:“polymorphic:Owner;” 标签详解
在 GORM 中使用 gorm:“polymorphic:Owner;” 标签,是为了实现多态关系,即允许一个模型(如 Toy)以通用的方式关联到多个其他模型(如 Cat 和 Dog)。
polymorphic:这部分指明了这是一个多态关联。
Owner:这里的 Owner 是一个标识符,用于指示多态关联的名称。GORM 会使用这个名称来生成关联字段的名称。
多态关联的字段生成规则
当你在 Toy 结构体中声明 gorm:“polymorphic:Owner;” 时,GORM 会自动基于 Owner 这个标识符生成两个字段:
OwnerID:这个字段用来存储拥有者的主键值。在这个例子中,如果 Toy 是属于 Dog,则 OwnerID 会存储那只 Dog 的 ID 值。
OwnerType:这个字段用来标识拥有者的类型。在多个不同的模型中使用相同的多态关联时,OwnerType 用于区分这个 Toy 属于哪一个模型,例如 “dogs” 或 “cats”。
多态关联是一个非常强大的数据库模型设计概念,它允许一个模型(在这个例子中是 Toy)关联到多个其他模型(如 Cat 和 Dog),而不需要为每个关联创建单独的外键列。在 GORM 中,这通过在 Toy 模型中使用两个字段 OwnerID 和 OwnerType 来实现,这两个字段共同定义了 Toy 的拥有者。
多态关联的工作原理
OwnerID 字段 存储了拥有者记录的主键值,即 Cat 或 Dog 表中某条记录的 ID。
OwnerType 字段 存储了拥有者的类型,用于区分 Toy 是属于 Cat 还是 Dog。 这通常是拥有者表名的字符串表示。
示例解析
根据提供的例子,当你创建一个 Dog 实例并给它分配一个 Toy 时,GORM 会自动填充 Toy 表的 OwnerID 和 OwnerType 字段,以表明这个 Toy 属于哪个 Dog 实例。
db.Create(&Dog{Name: "dog1", Toy: Toy{Name: "toy1"}})
这个操作会生成以下 SQL 语句:
1.首先,插入 Dog 记录到 dogs 表。
INSERT INTO `dogs` (`name`) VALUES ("dog1")
2.然后,插入 Toy 记录到 toys 表,同时设置 owner_id 为新插入的 Dog 记录的 ID,owner_type 设置为 “dogs” 来表明这个 Toy 属于 Dog 表的记录。
INSERT INTO `toys` (`name`,`owner_id`,`owner_type`) VALUES ("toy1", 1, "dogs")
多态关联的优势
灵活性:多态关联提供了极大的灵活性,允许一个模型实例关联到多个不同类型的模型实例上,而不需要为每种类型的关联创建单独的字段或表。
简化模型设计:通过减少需要的外键和表,多态关联可以简化数据库模型的设计,使其更加干净、简洁。
使用场景
多态关联特别适合那些需要将一个模型(如 Toy)与多个其他模型(如 Cat、Dog 等)进行灵活关联的情况。这种设计模式在实现如评论系统、收藏功能、图片上传等功能时特别有用,其中一个项(如评论、收藏、图片)可以属于应用中的多个不同模型(如文章、产品、用户等)。
总结:
考虑到上面的例子,通过在 Toy 结构体中使用 gorm:“polymorphic:Owner;” 标签,GORM 知道如何处理 Toy 和其他模型(Cat、Dog)之间的多态关系。这意味着,在数据库操作时,GORM 会自动设置 OwnerID 和 OwnerType 字段,以正确地反映每个 Toy 所属的实体。
您也可以实验标签polymorphicValue来更改多态类型的值,例如:
type Dog struct {
ID int
Name string
Toy Toy `gorm:"polymorphic:Owner;polymorphicValue:master"`
}
type Toy struct {
ID int
Name string
OwnerID int
OwnerType string
}
db.Create(&Dog{Name: "dog1", Toy: Toy{Name: "toy1"}})
// INSERT INTO `dogs` (`name`) VALUES ("dog1")
// INSERT INTO `toys` (`name`,`owner_id`,`owner_type`) VALUES ("toy1","1","master")
解读:
这段Go代码展示了如何在使用GORM(一个Go语言的ORM库)时,实现多态关联。多态关联是指一个模型可以属于多个其他模型中的任意一个。在这个例子中,Dog 和 Toy 模型通过多态方式相关联,这意味着一个玩具可以属于不同类型的拥有者(在这个例子中是 Dog),而不是仅限于一个特定的模型。
1.Dog 结构体代表狗,每只狗都有一个ID、一个名字,以及一个玩具(Toy)。在 Toy 结构体中,通过标签 gorm:“polymorphic:Owner;polymorphicValue:master” 指定了多态关联。这里,Owner 是多态字段的前缀。
2.Toy 结构体代表玩具,每个玩具有自己的ID、名字、拥有者ID (OwnerID),和拥有者类型 (OwnerType)。OwnerID 和 OwnerType 字段共同决定了玩具的拥有者。
3.在这个多态关联中,polymorphic:Owner 告诉GORM这是一个多态关联,Owner 是多态类型和ID字段的前缀。polymorphicValue:master 指定了当当前模型(在这个例子中是 Dog)作为拥有者时,OwnerType 字段的值应该是什么。 在这个例子中,无论何时创建一个 Dog 实例并给它分配一个玩具,OwnerType 都会被设置为 “master”。
4.当调用 db.Create(&Dog{Name: “dog1”, Toy: Toy{Name: “toy1”}}) 时,GORM 会先插入一个 Dog 记录,然后插入一个 Toy 记录。在插入 Toy 记录时,它会自动将 OwnerID 设置为刚刚插入的 Dog 记录的ID,并将 OwnerType 设置为 “master”。
总结,这段代码通过GORM的多态关联功能,演示了如何灵活地将 Toy 模型关联到 Dog 模型,同时允许 Toy 模型可能在未来关联到其他类型的模型上,通过指定 polymorphicValue 来明确指出拥有者类型。这种方式在数据库设计中非常有用,因为它提供了很高的灵活性和可扩展性。
预加载
GORM可以通过Preload、joins预加载has one关联的记录。
自引用Has One
type User struct {
gorm.Model
Name string
ManagerID *uint
Manager *User
}
解读:
这段Go代码展示了使用GORM库在Go中实现自引用关系的例子。在这个例子中,我们定义了一个 User 结构体,用来表示用户。这里使用的自引用关系是一种特殊的关联关系,它允许一个实体(在这个场景下是 User)引用另一个相同类型的实体。 这种模式在需要表示层级或树形结构的数据时非常有用,比如组织架构中的经理与下属关系。
具体来看 User 结构体的定义:
**gorm.Model:**这是GORM提供的一个基础模型,它自动包含了几个常见的字段,如 ID, CreatedAt, UpdatedAt, DeletedAt。使用它可以方便地为你的模型添加这些通用字段。
Name: 表示用户的名字,是一个字符串类型的字段。
ManagerID: 是一个指向 uint(无符号整型)的指针,用来存储管理该用户的经理的ID。这个字段是可选的,因为它是一个指针(在Go中,指针可以为 nil,表示没有值)。这样设计允许某些用户没有直接的经理(例如,公司的CEO可能没有经理)。
Manager: 是一个指向 User 类型的指针,表示这个用户的经理。 这个字段通过 ManagerID 与同一张表中的另一个 User 记录相关联。它实现了自引用关系**,即一个用户(下属)引用另一个用户(经理)作为它的经理。**
这种自引用结构允许你构建一种层次结构,例如公司的员工和管理层级。你可以通过 ManagerID 字段来设置某个用户的经理,然后通过 Manager 字段来访问这个经理的详细信息。例如,如果你想找到某个用户的经理的名字,你可以通过这个用户的 Manager 字段来访问。
外键约束
你可以通过constraint配置OnUpdate、OnDelete实现外键约束,在使用GORM进行迁移时就会被创建,例如
type User struct {
gorm.Model
CreditCard CreditCard `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}
type CreditCard struct {
gorm.Model
Number string
UserID uint
}
这个例子我认为很直观了。