has many 的实践
创建表
// 下面这个是典型的一对多的关系
// 看看自动生成的时候是否会自动生成外键
type User struct {
gorm.Model
Username string
Orders []Order
}
type Order struct {
gorm.Model
UserID uint
Price float64
}
/***********************************/
e := db.Debug().AutoMigrate(User{}, Order{})
if e != nil {
fmt.Println(e)
}
/***********************************/
自动迁移后生成了两个表格
通过Debug可以看到创建User于Order表的sql语句分别为:
CREATE TABLE `users` (
`id` bigint unsigned AUTO_INCREMENT,`created_at` datetime(3) NULL,
`updated_at` datetime(3) NULL,`deleted_at` datetime(3) NULL,
`username` longtext,
PRIMARY KEY (`id`),
INDEX `idx_users_deleted_at` (`deleted_at`)
)
CREATE TABLE `orders` (
`id` bigint unsigned AUTO_INCREMENT,`created_at` datetime(3) NULL,
`updated_at` datetime(3) NULL,
`deleted_at` datetime(3) NULL,
`user_id` bigint unsigned,`price` double,
PRIMARY KEY (`id`),
INDEX `idx_orders_deleted_at` (`deleted_at`),
CONSTRAINT `fk_users_orders`
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`)
)
可以看到在没有任何关于外键的注释的条件下,gorm自动识别出了这是一个一对多的关系,并在orders表中构造了外键约束。
分别在两个程序中创建User与Order会导致没有外键约束被创建
先创建Order后创建User也会无法自动创建外键。
只有先创建User后创建Order才会产生外键。
插入数据
下面我们尝试使用preload获取数据,在此之前需要先填入数据
这种相关联的数据要如何填入呢?
这里尝试插入数据
oder1 := Order{
Price: 100,
}
oder2 := Order{
Price: 200,
}
user := &User{Username: "test1", Orders: []Order{oder1, oder2}}
db.Debug().Create(&user)
稍微看了一下马士兵的课程之后得出两个推测
1、不用拘泥与gorm给出的四个模式,一个结构体是否包含另一个结构体的对象仅仅影响其是否可以接受数据,外键的构造仅由是否存在外键字段决定。(唯一的限制是结构体不能直接相互包含,举个例子在User中直接包含UserInformation 对象的条件下,UserInformation不能直接包含User对象)同时这些对象并不会变成表中的字段。
2、外键是否创建由是否包含外键字段决定,当Order包含“UserID”,就会创建映射到User对象ID的UserID这一外键。
验证猜想
设计并创建数据表
type User struct {
gorm.Model
Username string
Orders []Order
UserInformation UserInformation
}
type UserInformation struct {
gorm.Model
phone string
Location string
UserID uint
// 直接使用User会导致循环引用,使得编译器无法计算这个结构体占用空间大小
User *User
}
type Order struct {
gorm.Model
UserID uint
Price float64
Tags []Tag `gorm:"many2many:order_tag"`
}
type Tag struct {
gorm.Model
Name string
Orders []Order `gorm:"many2many:order_tag"`
}
简单来讲 User与Order是一对多的关系
User与UserInformation是一对一的关系
Order与Tag是多对多的关系
上面共四个结构体,尝试使用 db.AutoMigrate(&User{}, &Order{}, &Tag{}, &UserInformation{}) 创建表,预计创建五个表格。(包含一张连接表 order_tag)
来看看外键是否成功创建。
先看User_information,有的
Order
order_tag
可以看到外键都设置好了。
实验证明推测2成立。
操作关联
在创建各个对象并将数据存入数据库时,我们可以暂且不管对象间的关联,而是选择在创建完成后再添加、修改、删除关联。
我们先来单独创建一些数据。可以看到创建的时候并没有设置他们之间的关系。
user := User{
Username: "迪杰斯特拉",
}
order1 := Order{
Price: 100,
}
order2 := Order{
Price: 200,
}
db.Create(&user)
db.Create([]*Order{&order1, &order2})
但是会发现order无法插入,因为外键约束要求插入的order的UserID必须是User表中已经存在的值。
这里有一个十分有趣的方法可以绕过这个限制,只要将外键的类型设置为指针就可以正常插入了。
type Order struct {
gorm.Model
// 改成了指针类型
UserID *uint
User User
Price float64
Tags []Tag `gorm:"many2many:order_tag"`
}
再次运行代码成功插入数据,可以看到独立插入数据的user_id是空的。
新增关系(Append)
然后我们就能尝试创建关联了,为了避免麻烦我们在原有的独立插入数据的后面添加代码。
user := User{
Username: "迪杰斯特拉",
}
order1 := Order{
Price: 100,
}
order2 := Order{
Price: 200,
}
db.Create(&user)
db.Create([]Order{order1, order2})
db.Model(&user).Association("Orders").Append([]Order{order1, order2})
运行后发现往orders中重复插入了数据
这是因为Append针对四种模式有不同的效果
many to many、has many添加新的关联 (说人话就是用了 []Order),也就是添加新数据has one,belongs to替换当前的关联 (说人话就是用了 UserInformation),也就是替换原有的外键
而上面就是因为使用了 []order 所以是添加关联(同时创建外表信息与关联)
如果是使用order类型,就仅仅会修改外表对象的user_id项。
这里我们用userinformation测试一下,使用下面的代码测试,预期结果是只会有一条userinformation 并且UseID指向新建的对象。
user := User{
Username: "迪杰斯特拉",
}
//user.ID = 1
userinfo := UserInformation{
phone: "1234567890",
Location: "北京",
}
db.Create(&user)
fmt.Println(user.ID)
db.Create(&userinfo)
fmt.Println(userinfo.UserID)
db.Model(&user).Association("UserInformation").Append(&userinfo)
fmt.Println(user.UserInformation)
输出结果符合预期