Gorm关联相关的内容的实践 | 青训营笔记

92 阅读4分钟

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

输出结果符合预期