📦GORMv1.25.6

我这里创建表自动加前缀是因为在连接开始就设置了

🏆 GORM中表与表的关系

🍅 一对一

🌟 Belongs to

Belongs to会与另一个模型建立了一对一的连接。 这种模型的每一个实例都“属于”另一个模型的一个实例,也就是类中包含另外一个类比如这个用户拥有一个任务列表

要定义一个 Belongs to 关系,数据库的表中必须存在外键。默认情况下,外键的名字,使用拥有者的类型名称加上表的主键的字段名字

Belongs to它主要在 子项 模型的角度来出发,建立 model

这里我定义了一个这个任务属于哪个用户的,这里的 UserID就表示为一个隐式的外键,也就是逻辑外键

// User 创建结构体对应表
type User struct {
	ID        string `gorm:"primaryKey"`
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt gorm.DeletedAt `gorm:"index"`
	Name      string
	Password  string  `gorm:"not null"`
}

// Tasks 用户任务表
type Tasks struct {
	gorm.Model
	Title   string `gorm:"not null;comment: 任务标题"`
	Content string `gorm:"not null;comment: 任务内容"`
	Status  int    `gorm:"not null;comment: 任务状态"`
	UserID  string 	//外键,如果不指定默认就是和主键
	User    User 	//表示属于的用户
}

此时就可以创建表了,这里的顺序是先创建 User在创建 Tasks,应为 Tasks表需要依赖于 User

err = migrator.AutoMigrate(&model.User{}, &model.Tasks{})

⭐️ 在 GROM中会自动创建、更新,也就是说如果你只创建 Tasks表,但是它有依赖于 User,他就会帮你自动也把 User Modle也创建了

// User 创建结构体对应表
type User struct {
	gorm.Model
	Name     string
	Password string `gorm:"not null"`
}

// Tasks 用户任务表
type Tasks struct {
	gorm.Model
	Title   string `gorm:"not null;comment: 任务标题"`
	Content string `gorm:"not null;comment: 任务内容"`
	Status  int    `gorm:"not null;comment: 任务状态"`
	UserID  string //外键,如果不指定默认就是和主键
	User    User   //表示属于的用户
}

func main() {
	// 连接数据库,这里的连接是被我封装的
	db, err := untils.Dbuntils()
	if err != nil {
		println("连接数据库失败", err.Error())
	}
	println(db.Name())
	err = db.AutoMigrate(&Tasks{})
}

此时在 Belongs toUser可能并不知情有 Tasks,只有 Tasks知道它是属于哪个 User,这就是 belong to的关系,查看表结构

MariaDB [gorm]> desc gormuser;
+------------+---------------------+------+-----+---------+----------------+
| Field      | Type                | Null | Key | Default | Extra          |
+------------+---------------------+------+-----+---------+----------------+
| id         | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| created_at | datetime(3)         | YES  |     | NULL    |                |
| updated_at | datetime(3)         | YES  |     | NULL    |                |
| deleted_at | datetime(3)         | YES  | MUL | NULL    |                |
| name       | varchar(256)        | YES  |     | NULL    |                |
| password   | varchar(256)        | NO   |     | NULL    |                |
+------------+---------------------+------+-----+---------+----------------+
6 rows in set (0.015 sec)

MariaDB [gorm]> desc gormtasks;
+------------+---------------------+------+-----+---------+----------------+
| Field      | Type                | Null | Key | Default | Extra          |
+------------+---------------------+------+-----+---------+----------------+
| id         | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| created_at | datetime(3)         | YES  |     | NULL    |                |
| updated_at | datetime(3)         | YES  |     | NULL    |                |
| deleted_at | datetime(3)         | YES  | MUL | NULL    |                |
| title      | varchar(256)        | NO   |     | NULL    |                |
| content    | varchar(256)        | NO   |     | NULL    |                |
| status     | bigint(20)          | NO   |     | NULL    |                |
| user_id    | bigint(20) unsigned | YES  |     | NULL    |                |
+------------+---------------------+------+-----+---------+----------------+
8 rows in set (0.014 sec)

1️⃣ 重新外键和引用

默认情况下外键的名字,使用拥有者的类型名称加上表的主键的字段名字,如果你想修改,就直接在结构体中修改

下面代码 foreignKey用于指定当前模型(从表)中的外键字段,而 references用于指定该外键字段所关联的主表中的字段。

foreignKey:UserName 表示 Tasks 结构体中的 UserName 字段是外键字段。

⚠️ 如果外键名恰好在拥有者类型中存在,GORM 通常会错误的认为它是 has one 关系。我们需要在 belongs to 关系中指定 references

references:Name 表示这个外键字段 UserName 关联到主表(User 结构体对应的表)中的 Name 字段。

// User 创建结构体对应表
type User struct {
	ID        string `gorm:"primaryKey"`
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt gorm.DeletedAt `gorm:"index"`
	Name      string
	Password  string  `gorm:"not null"`
}

// Tasks 用户任务表
type Tasks struct {
	gorm.Model
	Title    string `gorm:"not null;comment: 任务标题"`
	Content  string `gorm:"not null;comment: 任务内容"`
	Status   int    `gorm:"not null;comment: 任务状态"`
	UserName string
	User     User `gorm:"foreignKey:UserName;references:Name"`
}

2️⃣关系创建

在创建 Tasks时需要先创建 User,应为它依赖于 User

//创建Task
user := User{
    Name:     "张三",
    Password: "123456",
}

task := Tasks{
    Title:   "学习Gorm",
    Content: "一对一,一对多关系",
    Status:  0,
    User:    user,
}

db.Create(&task)

执行创建,可见 gormtasks表和 gromuser中都增加了数据

MariaDB [gorm]> select  * from gormuser;
+----+-------------------------+-------------------------+------------+--------+----------+
| id | created_at              | updated_at              | deleted_at | name   | password |
+----+-------------------------+-------------------------+------------+--------+----------+
|  1 | 2024-06-29 21:44:25.178 | 2024-06-29 21:44:25.178 | NULL       | 张三   | 123456   |
+----+-------------------------+-------------------------+------------+--------+----------+
1 row in set (0.000 sec)

MariaDB [gorm]> select  * from gormtasks;
+----+-------------------------+-------------------------+------------+------------+-----------------------------+--------+---------+
| id | created_at              | updated_at              | deleted_at | title      | content                     | status | user_id |
+----+-------------------------+-------------------------+------------+------------+-----------------------------+--------+---------+
|  1 | 2024-06-29 21:44:25.200 | 2024-06-29 21:44:25.200 | NULL       | 学习Gorm   | 一对一,一对多关系          |      0 |       1 |
+----+-------------------------+-------------------------+------------+------------+-----------------------------+--------+---------+
1 row in set (0.000 sec)

此时你去查找一下这个任务表,查询需要一个接收对象,那么这个 tasks中又包含了 user,试一下查询 taskmodel的时候可不可以将属于的 user也查询出来

//接收对象
var task Tasks

//查询代码
db.First(&task, 1)
fmt.Println(task)

执行查看,会发现后面user属性那部分它是空的 {{0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {0001-01-01 00:00:00 +0000 UTC false}} },这是因为,你创建表存在在关联是在软件中,也就是在模型中,查询数据库的时候,数据库也无法将关联也直接查询出来,需要 预加载

{{1 2024-06-29 21:44:25.2 +0800 CST 2024-06-29 21:44:25.2 +0800 CST {0001-01-01 
00:00:00 +0000 UTC false}} 学习Gorm 一对一,一对多关系 0 1 {{0 0001-01-01 00:00:
00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC {0001-01-01 00:00:00 +0000 UTC false}
}  }}

使用预加载查询,预加载又两种方法一种是 Joins一种是 Preload

//Joins
var task Tasks

db.Joins("User").First(&task, 1)
fmt.Println(task)


//Preload
var task Tasks

db.Preload("User").First(&task, 1)
fmt.Println(task)


//执行结果
{{1 2024-06-29 21:44:25.2 +0800 CST 2024-06-29 21:44:25.2 +0800 CST {0001-01-01 
00:00:00 +0000 UTC false}} 学习Gorm 一对一,一对多关系 0 1 {{1 2024-06-29 21:44:
25.178 +0800 CST 2024-06-29 21:44:25.178 +0800 CST {0001-01-01 00:00:00 +0000 UT
C false}} 张三 123456}}

3️⃣外键约束

关联可以被看作是一个逻辑外键,所以可以直接在结构体中设置外键约束,你可以通过

constraint 标签配置 OnUpdateOnDelete 实现外键约束,这里设置了同步更新和删除后设置为NULL

⚠️ 注意如果在连接中开启了禁用外键,需要取消,如果要禁用需要注意以下几点:

  1. 数据库外键约束:数据库级别的外键约束是数据库管理系统(DBMS)用来保证数据完整性的一种机制。如果在外键约束被禁用的情况下,数据库不会自动检查或强制执行外键的引用完整性。
  2. GORM 标签定义:在 GORM 结构体中使用标签(例如 gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL")定义外键行为,这些定义是给 GORM 框架使用的,以便在 ORM 层面上理解和维护关系。这些标签定义了当主键更新或删除时,外键应如何响应。
  3. 外键约束的生效:如果你在数据库级别禁用了外键约束,即使在 GORM 标签中定义了外键行为,数据库也不会自动执行这些行为。但是,GORM 可以使用这些标签来生成相应的 SQL 语句,以在应用层面上模拟这些行为。
  4. 应用层面的模拟:当 GORM 执行创建、更新或删除操作时,它会根据结构体标签生成相应的 SQL 语句。例如,如果你定义了 OnDelete: SET NULLGORM 会生成 SQL 语句来更新或删除相关记录。但是,这完全依赖于 GORM 的操作,而不是数据库的自动约束。
  5. 数据一致性:在没有数据库外键约束的情况下,维护数据一致性的负担落在了应用逻辑上。你需要确保应用逻辑正确处理了外键的级联更新或删除,否则可能会导致数据不一致。
  6. 性能和并发:在没有数据库外键约束的情况下,某些操作可能会获得更好的性能,因为数据库不需要检查外键约束。但是,这可能会牺牲数据的一致性,特别是在高并发的应用中。
  7. 事务管理:即使外键约束在数据库级别被禁用,你仍然可以使用 GORM 的事务管理功能来确保操作的原子性。
// User 创建结构体对应表
type User struct {
	gorm.Model
	Name     string
	Password string `gorm:"not null"`
}

// Tasks 用户任务表
type Tasks struct {
	gorm.Model
	Title   string `gorm:"not null;comment: 任务标题"`
	Content string `gorm:"not null;comment: 任务内容"`
	Status  int    `gorm:"not null;comment: 任务状态"`
	UserID  string //外键,如果不指定默认就是和主键
	User    User   `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"` //表示属于的用户
}

测试一下

var task Tasks
db.Model(&Tasks{}).Preload("User").First(&task, 2)
fmt.Println("第一遍输出ID: ", task.UserID) //关联的是user的id为3的

db.Model(&User{}).Where("id = ?", 3).Update("id", "1")

db.Model(&Tasks{}).Preload("User").First(&task, 2)
fmt.Println("第二遍输出ID: ", task.UserID)


//输出
第一遍输出ID:  3
第二遍输出ID:  1

删除就不演示了,需要注意的是,由于 gorm使用的是软删除,不会直接删除数据,导致外键无法及时更新,就需要使用到 unscoped

🌟 Has One

has one 与另一个模型建立一对一的关联,但它和 Belogns to一对一关系有些许不同。 这种关联表明一个模型的每个实例都包含或拥有另一个模型的一个实例,在 belong to中我设置的是一个任务它 属于一个用户,而在 Has one中它表示这个用户 拥有一个任务

Has One 是在 父项的视角出发的

// User 创建结构体对应表
type User struct {
	gorm.Model
	Name     string `gorm:"unique;not null"`
	Password string `gorm:"not null"`
	Task     Tasks  //表示拥有一个Task
}

// Tasks 用户任务表
type Tasks struct {
	gorm.Model
	UserID  uint //表示它属于谁
	Title   string `gorm:"not null;comment: 任务标题"`
	Content string `gorm:"not null;comment: 任务内容"`
	Status  int    `gorm:"not null;comment: 任务状态"`
}

使用 Has One 创建数据表的话,就不可以向 Belongs to那样创建 子类就可以将 父类的表创建出来,反之也不行,所以在 has one创建中最好老老实实直接创建两个数据表,且需要先创建 User表在创建 Tasks,因为在创建 task表的时候时,由于关系需要,需要使用外键执行 user表中的 id

func main() {
	// 连接数据库
	db, err := untils.Dbuntils()
	if err != nil {
		println("连接数据库失败", err.Error())
	}
	println(db.Name())
	err = db.AutoMigrate(&User{}, &Tasks{})

}

查看这结构,此时 user表内是没有任何 task的数据的,但是 task表内的 user_idgorm中是和 user有关联的

MariaDB [gorm]> desc gormtasks;
+------------+---------------------+------+-----+---------+----------------+
| Field      | Type                | Null | Key | Default | Extra          |
+------------+---------------------+------+-----+---------+----------------+
| id         | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| created_at | datetime(3)         | YES  |     | NULL    |                |
| updated_at | datetime(3)         | YES  |     | NULL    |                |
| deleted_at | datetime(3)         | YES  | MUL | NULL    |                |
| user_id    | bigint(20) unsigned | YES  |     | NULL    |                |
| title      | varchar(256)        | NO   |     | NULL    |                |
| content    | varchar(256)        | NO   |     | NULL    |                |
| status     | bigint(20)          | NO   |     | NULL    |                |
+------------+---------------------+------+-----+---------+----------------+
8 rows in set (0.014 sec)

MariaDB [gorm]> desc gormuser;
+------------+---------------------+------+-----+---------+----------------+
| Field      | Type                | Null | Key | Default | Extra          |
+------------+---------------------+------+-----+---------+----------------+
| id         | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| created_at | datetime(3)         | YES  |     | NULL    |                |
| updated_at | datetime(3)         | YES  |     | NULL    |                |
| deleted_at | datetime(3)         | YES  | MUL | NULL    |                |
| name       | varchar(256)        | NO   | UNI | NULL    |                |
| password   | varchar(256)        | NO   |     | NULL    |                |
+------------+---------------------+------+-----+---------+----------------+
6 rows in set (0.013 sec)

MariaDB [gorm]>

1️⃣ 创建关系

belongs to中如果创建 Task就必须要有一个 user对象在 Has one也是一样的,或者直接通过 user来创建(注意:User创建并不依赖于 Task)

//Has one 创建关系
user := &User{
    Name:     "张三",
    Password: "123456",
    Task: Tasks{
        Title:   "学习ing...",
        Content: "学习Go,Gin,Gore,Go-zero",
        Status:  1,
        //这里的UserID不需要指定它会自动写入当前创建用户的
    },
}

db.Create(user)

同样的查找关系也是需要通过 PreloadJoins来查询

//查询
var user User
db.Preload("Task").First(&user, 4)
fmt.Println(user)

//输出
{{4 2024-07-01 23:15:27.488 +0800 CST 2024-07-01 23:15:27.488 +0800 CST {0001-01-01 00:00:00 +0000 UTC false}
} 张三 123456 {{2 2024-07-01 23:15:27.512 +0800 CST 2024-07-01 23:15:27.512 +0800 CST {0001-01-01 00:00:00 +0
000 UTC false}} 4 学习ing... 学习Go,Gin,Gore,Go-zero 1}}

2️⃣ 重写外键和引用

同样也是重写外键的引用

// User 创建结构体对应表
type User struct {
	ID        string `gorm:"primaryKey"`
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt gorm.DeletedAt `gorm:"index"`
	Name      string         `gorm:"unique;not null"`
	Password  string         `gorm:"not null"`
	Task      Tasks          `gorm:"foreignKey: UserName;reference:name"` //表示拥有一个Task,且引用的是name字段
}


// Tasks 用户任务表
type Tasks struct {
	gorm.Model
	UserName string
	Title    string `gorm:"not null;comment: 任务标题"`
	Content  string `gorm:"not null;comment: 任务内容"`
	Status   int    `gorm:"not null;comment: 任务状态"`
}

重写创建表,主要着重看tasks表,会发现多了一个 user_name,但是 user_id没有删除,在介绍 Belongs to已经介绍过了

MariaDB [gorm]> DESC gormtasks;
+------------+---------------------+------+-----+---------+----------------+
| Field      | Type                | Null | Key | Default | Extra          |
+------------+---------------------+------+-----+---------+----------------+
| id         | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| created_at | datetime(3)         | YES  |     | NULL    |                |
| updated_at | datetime(3)         | YES  |     | NULL    |                |
| deleted_at | datetime(3)         | YES  | MUL | NULL    |                |
| user_id    | varchar(256)        | YES  |     | NULL    |                |
| title      | varchar(256)        | NO   |     | NULL    |                |
| content    | varchar(256)        | NO   |     | NULL    |                |
| status     | bigint(20)          | NO   |     | NULL    |                |
| user_name  | varchar(256)        | YES  |     | NULL    |                |
+------------+---------------------+------+-----+---------+----------------+
9 rows in set (0.044 sec)

2️⃣ 自引用(套娃)

这里指定了一个经理,当然我觉得在创建一个经理 model更加好一点

// User 创建结构体对应表
type User struct {
	ID        string `gorm:"primaryKey"`
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt gorm.DeletedAt `gorm:"index"`
	Name      string         `gorm:"unique;not null"`
	Password  string         `gorm:"not null"`
	Task      Tasks          `gorm:"foreignKey: UserName;reference:name"` //表示拥有一个Task,且引用的是name字段
	ManagerID string
	Manager   *User  //表示一个用户拥有一个上级
}

3️⃣ 外键约束

你可以通过为标签 constraint 配置 OnUpdateOnDelete 实现外键约束,在使用 GORM 进行迁移时它会被创建(Belongs to也可以)

// User 创建结构体对应表
type User struct {
	gorm.Model
	Name     string `gorm:"unique;not null"`
	Password string `gorm:"not null"`
	Task     Tasks  `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"` //表示拥有一个Task,且引用的是name字段
}

// Tasks 用户任务表
type Tasks struct {
	gorm.Model
	UserID  uint
	Title   string `gorm:"not null;comment: 任务标题"`
	Content string `gorm:"not null;comment: 任务内容"`
	Status  int    `gorm:"not null;comment: 任务状态"`
}

创建两个表的关联字段然后删除用户字段,查看 task字段会不会改变

package main

import (
	"GORM_STU/GORM_Connect/untils"
	"fmt"
	"gorm.io/gorm"
)

// User 创建结构体对应表
type User struct {
	gorm.Model
	Name     string `gorm:"unique;not null"`
	Password string `gorm:"not null"`
	Task     Tasks  `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"` //表示拥有一个Task,且引用的是name字段
}

// Tasks 用户任务表
type Tasks struct {
	gorm.Model
	UserID  uint
	Title   string `gorm:"not null;comment: 任务标题"`
	Content string `gorm:"not null;comment: 任务内容"`
	Status  int    `gorm:"not null;comment: 任务状态"`
}

func main() {
	// 连接数据库
	db, err := untils.Dbuntils()
	if err != nil {
		println("连接数据库失败", err.Error())
	}
	println(db.Name())
	err = db.AutoMigrate(&User{}, &Tasks{})

	//Has one 创建关系
	user := &User{
		Name:     "张三",
		Password: "123456",
		Task: Tasks{
			Title:   "学习ing...",
			Content: "学习Go,Gin,Gore,Go-zero",
			Status:  1,
		},
	}

	db.Debug().Create(user)

	//查询任务
	var user2 User
	db.Model(&User{}).Preload("Task").First(&user2, 1)
	//删除用户
	db.Debug().Unscoped().Delete(&user2)
	//查询删除用户后的任务字段
	var task Tasks
	db.Debug().Model(&Tasks{}).First(&task)
	fmt.Println(task)
}

🍅 一对多

🌟 Has Many

has many 与另一个模型建立了一对多的连接,拥有者可以拥有零个或者多个关联模型

比如用户和任务,一个用户可以拥有多个任务

// User 创建结构体对应表
type User struct {
    gorm.Model
	Name      string         `gorm:"unique;not null"`
	Password  string         `gorm:"not null"`
	Task      []Tasks        //拥有多个用户
}

// Tasks 用户任务表
type Tasks struct {
	gorm.Model
	UserID string //这里需要也是user_id,默认及是主键
	Title    string `gorm:"not null;comment: 任务标题"`
	Content  string `gorm:"not null;comment: 任务内容"`
	Status   int    `gorm:"not null;comment: 任务状态"`
}

创建数据库,这里的创建方式和 has one一样需要先创建 User表在创建 Tasks

// 连接数据库
db, err := untils.Dbuntils()
if err != nil {
    println("连接数据库失败", err.Error())
}
println(db.Name())
err = db.AutoMigrate(&User{}, &Tasks{})

在查看一下表结构,会发现和 has one 一模一样的,在表结构相同,但是在 gorm中,user类可以拥有多个 tasks

MariaDB [gorm]> desc gormuser;
+------------+---------------------+------+-----+---------+----------------+
| Field      | Type                | Null | Key | Default | Extra          |
+------------+---------------------+------+-----+---------+----------------+
| id         | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| created_at | datetime(3)         | YES  |     | NULL    |                |
| updated_at | datetime(3)         | YES  |     | NULL    |                |
| deleted_at | datetime(3)         | YES  | MUL | NULL    |                |
| name       | varchar(256)        | NO   | UNI | NULL    |                |
| password   | varchar(256)        | NO   |     | NULL    |                |
+------------+---------------------+------+-----+---------+----------------+
6 rows in set (0.016 sec)

MariaDB [gorm]> desc gormtasks;
+------------+---------------------+------+-----+---------+----------------+
| Field      | Type                | Null | Key | Default | Extra          |
+------------+---------------------+------+-----+---------+----------------+
| id         | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| created_at | datetime(3)         | YES  |     | NULL    |                |
| updated_at | datetime(3)         | YES  |     | NULL    |                |
| deleted_at | datetime(3)         | YES  | MUL | NULL    |                |
| user_id    | bigint(20) unsigned | YES  | MUL | NULL    |                |
| title      | varchar(256)        | NO   |     | NULL    |                |
| content    | varchar(256)        | NO   |     | NULL    |                |
| status     | bigint(20)          | NO   |     | NULL    |                |
+------------+---------------------+------+-----+---------+----------------+
8 rows in set (0.013 sec)

1️⃣ 创建关联关系

这里的逻辑和 Has one一样,只不过 Tasks数量变多了

//Has Many创建关系
user := &User{
    Name:     "张三",
    Password: "123456",
    Task: []Tasks{{
        Title:   "学习ing...",
        Content: "学习Go,Gin,Gore,Go-zero",
        Status:  1,
    },
        {
            Title:   "运动ing...",
            Content: "跑步、跳绳、羽毛球",
            Status:  1,
        }},
}

db.Debug().Create(user)

查询和列出和 user有关联的数据

var user User
db.Preload("Task").First(&user, 1)
fmt.Println(user)

//输出
{{1 2024-07-02 23:03:51.133 +0800 CST 2024-07-02 23:03:51.133 +0800 CST {0001-01
-01 00:00:00 +0000 UTC false}} 张三 123456 [{{1 2024-07-02 23:03:51.154 +0800 CS
T 2024-07-02 23:03:51.154 +0800 CST {0001-01-01 00:00:00 +0000 UTC false}} 1 学 
习ing... 学习Go,Gin,Gore,Go-zero 1} {{2 2024-07-02 23:03:51.154 +0800 CST 202
4-07-02 23:03:51.154 +0800 CST {0001-01-01 00:00:00 +0000 UTC false}} 1 运动ing.
.. 跑步、跳绳、羽毛球 1}]}

它的各种操作都和 Has one差不多

🍅 多对多

🌟 Many to Many

Many to Many不同于前面两种关系,它在两个模型之间建立一个关联表,拿文档举例吧,一个人可以编写多个文档,多个人也可以编写一个文档

// User 创建结构体对应表
type User struct {
	gorm.Model
	Name     string `gorm:"unique;not null"`
	Password string `gorm:"not null"`
	Doc      []Docs `gorm:"many2many:user_doc"` // 多对多关系,使用many2many:user_doc 表示中间表
}

// Docs 用户任务表
type Docs struct {
	gorm.Model
	Title string `gorm:"not null;comment: 文章Title"`
	User  []User `gorm:"many2many:user_doc"` // 多对多关系,使用many2many:user_doc 表示中间表
}

创建表

db, err := untils.Dbuntils()
if err != nil {
    println("连接数据库失败", err.Error())
}
println(db.Name())
err = db.AutoMigrate(&User{}, &Docs{})

创建完成后会发现有三个表,其中一个为关联表

MariaDB [gorm]> show tables;
+----------------+
| Tables_in_gorm |
+----------------+
| gormdoc        |
| gormuser       |
| gormuser_doc   |
+----------------+
3 rows in set (0.001 sec)

查看表结构

MariaDB [gorm]> desc gormuser;
+------------+---------------------+------+-----+---------+----------------+
| Field      | Type                | Null | Key | Default | Extra          |
+------------+---------------------+------+-----+---------+----------------+
| id         | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| created_at | datetime(3)         | YES  |     | NULL    |                |
| updated_at | datetime(3)         | YES  |     | NULL    |                |
| deleted_at | datetime(3)         | YES  | MUL | NULL    |                |
| name       | varchar(256)        | NO   | UNI | NULL    |                |
| password   | varchar(256)        | NO   |     | NULL    |                |
+------------+---------------------+------+-----+---------+----------------+
6 rows in set (0.040 sec)

MariaDB [gorm]> desc gormdoc;
+------------+---------------------+------+-----+---------+----------------+
| Field      | Type                | Null | Key | Default | Extra          |
+------------+---------------------+------+-----+---------+----------------+
| id         | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| created_at | datetime(3)         | YES  |     | NULL    |                |
| updated_at | datetime(3)         | YES  |     | NULL    |                |
| deleted_at | datetime(3)         | YES  | MUL | NULL    |                |
| user_id    | varchar(256)        | YES  |     | NULL    |                |
| title      | varchar(256)        | NO   |     | NULL    |                |
+------------+---------------------+------+-----+---------+----------------+
6 rows in set (0.040 sec)

MariaDB [gorm]> desc gormuser_doc;
+---------+---------------------+------+-----+---------+-------+
| Field   | Type                | Null | Key | Default | Extra |
+---------+---------------------+------+-----+---------+-------+
| user_id | bigint(20) unsigned | NO   | PRI | NULL    |       |
| doc_id  | bigint(20) unsigned | NO   | PRI | NULL    |       |
+---------+---------------------+------+-----+---------+-------+
2 rows in set (0.039 sec)

MariaDB [gorm]>

查看关联表的 sql语句

CREATE TABLE `gormuser_doc` (
  `user_id` bigint(20) unsigned NOT NULL,
  `doc_id` bigint(20) unsigned NOT NULL,
  PRIMARY KEY (`user_id`,`doc_id`),
  KEY `fk_gormuser_doc_doc` (`doc_id`),
  CONSTRAINT `fk_gormuser_doc_doc` FOREIGN KEY (`doc_id`) REFERENCES `gormdoc` (`id`),
  CONSTRAINT `fk_gormuser_doc_user` FOREIGN KEY (`user_id`) REFERENCES `gormuser` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

1️⃣ 创建关联关系

package main

import (
	"GORM_STU/GORM_Connect/untils"
	"gorm.io/gorm"
)

// User 创建结构体对应表
type User struct {
	gorm.Model
	Name     string `gorm:"unique;not null"`
	Password string `gorm:"not null"`
	Doc      []Docs `gorm:"many2many:user_doc"` // 多对多关系,使用many2many:user_doc 表示中间表
}

// Docs 用户任务表
type Docs struct {
	gorm.Model
	Title string `gorm:"not null;comment: 文章Title"`
	User  []User `gorm:"many2many:user_doc"` // 多对多关系,使用many2many:user_doc 表示中间表
}

func main() {
	// 连接数据库
	db, err := untils.Dbuntils()
	if err != nil {
		println("连接数据库失败", err.Error())
	}
	println(db.Name())
	err = db.AutoMigrate(&User{}, &Docs{})
	//
	////Many to Many创建关系
	u1 := &User{
		Name:     "tanc",
		Password: "1234567",
		Doc: []Docs{
			{Title: "Gorm学习笔记"},
			{Title: "Go基础学习笔记"},
		},
	}

	d1 := &Docs{
		Title: "学习Gin",
		User: []User{
			*u1,
			{Name: "zs", Password: "123456"},
		},
	}

    //创建用户
	db.Debug().Create(u1)
  
    //创建Doc
	db.Debug().Create(d1)
}

创建后查看关联表即可,这里表示为 1和2的doc可以被 1号用户操作,3号的doc,可以被 1号和二号用户操作

MariaDB [gorm]> select * from gormuser_doc;
+---------+---------+
| docs_id | user_id |
+---------+---------+
|       1 |       1 |
|       2 |       1 |
|       3 |       1 |
|       3 |       2 |
+---------+---------+
4 rows in set (0.001 sec)

2️⃣ 自定义连接表

gorm自动创建的连接表,只有两个表的主键,没有其他信息,在有些情况下不能满足需求,就可以自定义连接表

自定义 连接表 可以是一个功能齐全的模型,比如支持 软删除钩子函数功能,并且可以具有更多字段

注意这里有一个小小的坑那就是,在连接表中你的主键字段需要和关联的结构体名字相同,比如 User结构体,在自定义连接表中就需要定义 UserID,而不是 UsersID

// User 创建结构体对应表
type User struct {
	gorm.Model
	Name     string `gorm:"unique;not null"`
	Password string `gorm:"not null"`
	Doc      []Docs `gorm:"many2many:user_doc"` // 多对多关系,使用many2many:user_doc 表示中间表
}

// Docs 用户任务表
type Docs struct {
	gorm.Model
	Title string `gorm:"not null;comment: 文章Title"`
	User  []User `gorm:"many2many:user_doc"` // 多对多关系,使用many2many:user_doc 表示中间表
}

// UserDoc 自定义连接表
type UserDoc struct {
	UserID    uint `gorm:"primaryKey"`
	DocsID    uint `gorm:"primaryKey"`
	CreatedAt time.Time
	DeletedAt gorm.DeletedAt
}

func main() {
	// 连接数据库
	db, err := untils.Dbuntils()
	if err != nil {
		println("连接数据库失败", err.Error())
	}

	//创建关联
	err = db.SetupJoinTable(&User{}, "Tasks", &UserDoc{})
	if err != nil {
		println("创建关联失败", err.Error())
	}

	err = db.SetupJoinTable(&Task{}, "Users", &UserDoc{})
	if err != nil {
		println("创建关联失败", err.Error())
	}

	//创建数据表
	err = db.AutoMigrate(&User{}, &Task{}, &UserDoc{})
	if err != nil {
		println("创建数据表失败", err.Error())
	}
}

创建后查看连接表结构

MariaDB [gorm]> desc gormuser_doc;
+------------+---------------------+------+-----+---------+-------+
| Field      | Type                | Null | Key | Default | Extra |
+------------+---------------------+------+-----+---------+-------+
| user_id    | bigint(20) unsigned | NO   | PRI | NULL    |       |
| docs_id    | bigint(20) unsigned | NO   | PRI | NULL    |       |
| created_at | datetime(3)         | YES  |     | NULL    |       |
| deleted_at | datetime(3)         | YES  |     | NULL    |       |
+------------+---------------------+------+-----+---------+-------+
4 rows in set (0.004 sec)

sql语句

CREATE TABLE `gormuser_doc` (
  `user_id` bigint(20) unsigned NOT NULL,
  `docs_id` bigint(20) unsigned NOT NULL,
  `created_at` datetime(3) DEFAULT NULL,
  `deleted_at` datetime(3) DEFAULT NULL,
  PRIMARY KEY (`user_id`,`docs_id`),
  KEY `fk_gormuser_doc_docs` (`docs_id`),
  CONSTRAINT `fk_gormuser_doc_docs` FOREIGN KEY (`docs_id`) REFERENCES `gormdocs` (`id`),
  CONSTRAINT `fk_gormuser_doc_user` FOREIGN KEY (`user_id`) REFERENCES `gormuser` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

3️⃣ 自定义主键

有时候默认组件ID是无法满足需求的,那就可以自定义主键

  1. 使用 foreingKey来定义结构体外键
  2. 使用 joinForeignKey来定义外键连接字段
  3. 使用 References来定义关联结构体的外键字段
  4. 使用 joinReferences来定义关联表单中的外键字段
// User 创建结构体对应表
type User struct {
	gorm.Model
	Name     string `gorm:"unique;not null;index:,unique"`
	Password string `gorm:"not null"`
	Doc      []Docs `gorm:"many2many:user_doc;foreignKey: Name;joinForeignKey:UserName;References: Title;JoinReferences: DocsName;"` // 多对多关系,使用many2many:user_doc 表示中间表
}

// Docs 用户任务表
type Docs struct {
	gorm.Model
	Title string `gorm:"not null;comment: 文章Title;index:,unique"`
	User  []User `gorm:"many2many:user_doc;foreignKey: Title;joinForeignKey:DocsName;References: Name;JoinReferences:UserName;"` // 多对多关系,使用many2many:user_doc 表示中间表
}

// UserDoc 自定义连接表
type UserDoc struct {
	UserName  string `gorm:"primaryKey;"`
	DocsName  string `gorm:"primaryKey"`
	CreatedAt time.Time
	DeletedAt gorm.DeletedAt
}

创建表,主要是查看自定义连接表中的代码

CREATE TABLE `gormuser_doc` (
  `user_name` varchar(256) NOT NULL,
  `docs_name` varchar(256) NOT NULL,
  `created_at` datetime(3) DEFAULT NULL,
  `deleted_at` datetime(3) DEFAULT NULL,
  PRIMARY KEY (`user_name`,`docs_name`),
  KEY `fk_gormuser_doc_docs` (`docs_name`),
  CONSTRAINT `fk_gormuser_doc_docs` FOREIGN KEY (`docs_name`) REFERENCES `gormdocs` (`title`),
  CONSTRAINT `fk_gormuser_doc_user` FOREIGN KEY (`user_name`) REFERENCES `gormuser` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

🍅关联标签(Association Tags)

GORM中的关联标签通常用于指定如何处理模型之间的关联。 这些标签定义了一些关系细节,比如外键,引用和约束。 理解这些标签对于有效地建立和管理模型之间的关系而言至关重要。

标签 描述
foreignKey 指定用作连接表中外键的当前模型的列名。
references 指示连接表的外键映射到的引用表中的列名。
polymorphic 定义多态类型,通常是模型名称。
polymorphicValue 设置多态值,通常是表名,如果没有另行指定。
many2many 命名多对多关系中使用的连接表。
joinForeignKey 标识连接表中映射回当前模型表的外键列。
joinReferences 指向与参考模型表相链接的连接表中的外键列。
constraint 为关联指定关系约束 OnUpdate,如。OnDelete