1.什么是ORM?

orm是一种术语而不是软件

  1. orm英文全称object relational mapping,就是 ==对象映射关系== 程序;
  2. 简单来说类似python这种面向对象的程序来说一切皆对象,但是我们使用的数据库却都是关系型的;
  3. 为了保证一致的使用习惯,通过 orm将编程语言的对象模型和数据库的关系模型建立映射关系;
  4. 这样我们直接 使用编程语言的对象模型进行操作数据库 就可以了,而不用直接使用sql语言;

2.什么是GORM?

GORM 官方文档

  • 全功能 ORM (无限接近)
  • 关联 (Has One, Has Many, Belongs To, Many To Many, 多态)
  • 钩子 (在创建/保存/更新/删除/查找之前或之后)
  • 预加载
  • 事务
  • 复合主键
  • SQL 生成器
  • 数据库自动迁移
  • 自定义日志
  • 可扩展性, 可基于 GORM 回调编写插件
  • 所有功能都被测试覆盖
  • 开发者友好

3.GORM(v2)基本使用

3.1安装

go get -u gorm.io/gorm

4.准备MySQL环境

4.1docker拉起一个mysql

docker run --name fly-mysql -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 -d mysql:8.0

4.2创建数据库

create database test_db charset utf8mb4;
use test_db;
show tables;

4.3连接数据库

package main

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

// 数据库初始化
func main() {
    //定义db连接串
    //parseTime=True&loc=Local
    //parseTime查询结果示范自带解析为时间。查询数据时将mysql_datetime字段字段转成go_time.time
    //loc是mysql的时区设置
    dsn := "root:123456@tcp(127.0.0.1:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"

    //建立连接
    mdb, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic(err)
    }
}

4.4自动迁移表

package main

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

// 1.表对应的struct
type User struct {
    ID       int64 `gorm:"primary_key"`
    Username string
    Password string
}

// 2.自定义表名
func (*User) TableName() string {
    return "user_t"
}

// 数据库初始化
func main() {
    dsn := "root:123456@tcp(127.0.0.1:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"

    //0.建立连接
    mdb, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic(err)
    }

    //3.自带创建表,AutoMigrate没有建立映射关系
    // 用于项目初始化自动创建表
    mdb.AutoMigrate(User{})
}

5.增删改查

package main

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

// 1.表对应的struct
type User struct {
    ID       int64 `gorm:"primary_key"`
    Username string
    Password string
}

// 2.自定义表名
func (*User) TableName() string {
    return "user"
}

// 数据库初始化
func main() {
    dsn := "root:123456@tcp(127.0.0.1:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"

    //0.建立连接
    mdb, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic(err)
    }

    //3.自带创建表,AutoMigrate没有建立映射关系
    // 用于项目初始化自动创建表,已存在不会重复创建
    mdb.AutoMigrate(User{})

    //新增
    mdb.Create(&User{
        //ID:       0, 自增属性不需要控制,所以注释掉
        Username: "sky",
        Password: "123456",
    })
}

测试

fei@feideMBP 03crud % go run main.go
fei@feideMBP 03crud % 

mysql> select * from user;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | sky      | 123456   |
+----+----------+----------+
1 row in set (0.00 sec)

package main

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

// 1.表对应的struct
type User struct {
    ID       int64 `gorm:"primary_key"`
    Username string
    Password string
}

// 2.自定义表名
func (*User) TableName() string {
    return "user"
}

// 数据库初始化
func main() {
    dsn := "root:123456@tcp(127.0.0.1:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"

    //0.建立连接
    mdb, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic(err)
    }

    //3.自带创建表,AutoMigrate没有建立映射关系
    // 用于项目初始化自动创建表,已存在不会重复创建
    mdb.AutoMigrate(User{})

    //新增 (必须接收指针类型)
    mdb.Create(&User{
        //ID:       0, 自增属性不需要控制,所以注释掉
        Username: "sky02",
        Password: "123456",
    })

    //修改
    id := 1
    //where 里面是原生的查询条件
    mdb.Model(User{}).Where("id = ?", id).Update("username", "fei")
    /*
        等价于:
            UPDATE user SET username='fei' WHERE id=1;
            Model()传入User结构体,自动去找该结构体小写复数即users,如果指定了func (*User) TableName() string 方法则找方法中对应的表名。

        其他写法
            mdb.Model(User{ID: 1,}).Update("username", "fei")
            mdb.Table("user").Where("id = ?", id).Update("username", "fei")
    */

}

测试

fei@feideMBP 03crud % go run main.go
fei@feideMBP 03crud % 

mysql> select * from user;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | fei      | 123456   |
|  2 | sky02    | 123456   |
+----+----------+----------+
2 rows in set (0.00 sec)
//将第一次加的sky改名成了fei
//第二次运行的create 又插入了一条数据

package main

import (
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    "log"
)

// 1.表对应的struct
type User struct {
    ID       int64 `gorm:"primary_key"`
    Username string
    Password string
}

// 2.自定义表名
func (*User) TableName() string {
    return "user"
}

// 数据库初始化
func main() {
    dsn := "root:123456@tcp(127.0.0.1:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"

    //0.建立连接
    mdb, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic(err)
    }

    //3.自带创建表,AutoMigrate没有建立映射关系
    // 用于项目初始化自动创建表,已存在不会重复创建
    mdb.AutoMigrate(User{})

    //查询 第一种
    u := &User{ID: 1}
    mdb.First(u)
    log.Printf("第一种查询写法:%v", u)

    //查询 第二种
    u2 := &User{}
    mdb.Where("id = ?", 2).First(u2)
    log.Printf("第二种查询写法:%v", u2)

    //查询所有数据
    users := []User{}
    mdb.Find(&users)
    log.Printf("查询全部:%v", users)
}

测试

fei@feideMBP 03crud % go run main.go
2024/10/05 22:47:25 第一种查询写法:&{1 fei 123456}
2024/10/05 22:47:25 第二种查询写法:&{2 sky02 123456}
2024/10/05 22:47:25 查询全部:[{1 fei 123456} {2 sky02 123456}]

package main

import (
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    "log"
)

// 1.表对应的struct
type User struct {
    ID       int64 `gorm:"primary_key"`
    Username string
    Password string
}

// 2.自定义表名
func (*User) TableName() string {
    return "user"
}

// 数据库初始化
func main() {
    dsn := "root:123456@tcp(127.0.0.1:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"

    //0.建立连接
    mdb, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic(err)
    }

    //3.自带创建表,AutoMigrate没有建立映射关系
    // 用于项目初始化自动创建表,已存在不会重复创建
    mdb.AutoMigrate(User{})

    users := []User{}
    mdb.Find(&users)
    log.Printf("删除前查询全部:%v", users)

    //删除,传入结构体表示在哪张表中删除
    mdb.Where("id = ?", 1).Delete(&User{})
    log.Printf("删除完成")

    //查询所有数据
    mdb.Find(&users)
    log.Printf("删除后查询全部:%v", users)
}

测试

fei@feideMBP 03crud % go run main.go
2024/10/05 22:53:38 删除前查询全部:[{1 fei 123456} {2 sky02 123456}]
2024/10/05 22:53:38 删除完成
2024/10/05 22:53:38 删除后查询全部:[{2 sky02 123456}]

5.1错误处理

package main

import (
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    "log"
)

// 1.表对应的struct
type User struct {
    ID       int64 `gorm:"primary_key"`
    Username string
    Password string
}

// 2.自定义表名
func (*User) TableName() string {
    return "user"
}

// 数据库初始化
func main() {
    dsn := "root:123456@tcp(127.0.0.1:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"

    //0.建立连接
    mdb, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic(err)
    }

    //3.自带创建表,AutoMigrate没有建立映射关系
    // 用于项目初始化自动创建表,已存在不会重复创建
    mdb.AutoMigrate(User{})

    users := []User{}
    mdb.Find(&users)
    log.Printf("查询全部:%v", users)

    //错误处理演示
    u2 := &User{}
    tx := mdb.Where("id = ?", 1).First(u2)
    //注意,tx.Error。查询出的数据为空也会算成err "record not found"
    if tx.Error != nil {
        log.Printf("查询报错")
    }

    //处理方式
    if tx.Error != nil && tx.Error != gorm.ErrRecordNotFound {
        log.Printf("查询报错2")
    }
}

测试

fei@feideMBP 03crud % go run main.go
2024/10/05 23:03:24 查询全部:[{2 sky02 123456}]

2024/10/05 23:03:24 /Volumes/data/go/src/test/03crud/main.go:41 record not found
[1.660ms] [rows:0] SELECT * FROM `user` WHERE id = 1 ORDER BY `user`.`id` LIMIT 1
2024/10/05 23:03:24 查询报错

6.模型定义

文档参考

6.1模型定义

  • 模型一般都是普通的 Golang 的结构体,Go的基本数据类型,或者指针。
package main

import (
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    "time"
)

// 不建议将字段属性放到结构体中定义
type User struct {
    Id           int64 `gorm:"primary_key" json:"id"`
    Name         string
    CreatedAt    *time.Time `json:"createdAt" gorm:"column:create_at"` //定义字段名
    Email        string     `gorm:"type:varchar(100);unique_index"`    // 唯一索引
    Role         string     `gorm:"size:255"`                          //设置字段的大小为255个字节
    MemberNumber *string    `gorm:"unique;not null"`                   // 设置字段唯一,且不为空
    Num          int        `gorm:"AUTO_INCREMENT"`                    // 设置 Num字段自增
    Address      string     `gorm:"index:addr"`                        // 给Address 创建一个名字是 `addr`的索引
    IgnoreMe     int        `gorm:"-"`                                 //忽略这个字段
}

func main() {
    dsn := "root:123456@tcp(127.0.0.1:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"

    //0.建立连接
    mdb, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic(err)
    }

    //3.自带创建表,AutoMigrate没有建立映射关系
    // 用于项目初始化自动创建表,已存在不会重复创建
    mdb.AutoMigrate(User{})
}

测试

fei@feideMBP 04mode % go run main.go 
fei@feideMBP 04mode % 

mysql> desc users;
+---------------+--------------+------+-----+---------+----------------+
| Field         | Type         | Null | Key | Default | Extra          |
+---------------+--------------+------+-----+---------+----------------+
| id            | bigint       | NO   | PRI | NULL    | auto_increment |
| name          | longtext     | YES  |     | NULL    |                |
| create_at     | datetime(3)  | YES  |     | NULL    |                |
| email         | varchar(100) | YES  |     | NULL    |                |
| role          | varchar(255) | YES  |     | NULL    |                |
| member_number | varchar(191) | NO   | UNI | NULL    |                |
| num           | bigint       | YES  |     | NULL    |                |
| address       | varchar(191) | YES  | MUL | NULL    |                |
+---------------+--------------+------+-----+---------+----------------+
8 rows in set (0.01 sec)

6.2支持结构标签

  • 标签是声明模型时可选的标记。
标签 说明
Column 指定列的名称
Type 指定列的类型
Size 指定列的大小,默认是 255
PRIMARY_KEY 指定一个列作为主键
UNIQUE 指定一个唯一的列
DEFAULT 指定一个列的默认值
PRECISION 指定列的数据的精度
NOT NULL 指定列的数据不为空
AUTO_INCREMENT 指定一个列的数据是否自增
INDEX 创建带或不带名称的索引,同名创建复合索引
UNIQUE_INDEX 类似 索引 ,创建一个唯一的索引
EMBEDDED 将 struct 设置为 embedded
EMBEDDED_PREFIX 设置嵌入式结构的前缀名称
- 忽略这些字段

7.一对多

7.1has many介绍

https://gorm.io/zh_CN/docs/has_many.html

  • has many关联就是创建和另一个模型的一对多关系。不同于has one,拥有者可以有零或多个关联模型。
  • 例如,例如每一个用户都拥有多张信用卡,这样就是生活中一个简单的一对多关系。

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

// 必须要写UserID这个外键属性
type CreditCard struct {
    gorm.Model
    Number string
    UserID uint // 默认会在 CreditCard 表中生成 UserID 字段作为 与User表关联的外键ID
}

// mdb.AutoMigrate(User{}, CreditCard{})

7.2外键

  • 为了定义一对多关系, 外键是必须存在的,默认外键的名字是所有者类型的名字加上它的主键(UserId)
  • 就像上面的例子,为了定义一个属于 User 的模型,外键就应该为 UserID。
  • 使用其他的字段名作为外键, 可以通过foreignkey来定制它。

type User struct {
  gorm.Model
  // foreignkey:UserRefer 可以自己指定外键关联字段名为:UserRefer
  // 默认外键字段名是 UserId,你也可以自己修改
  CreditCards []CreditCard `gorm:"foreignkey:UserRefer"`
}
type CreditCard struct {
  gorm.Model
  Number   string
  UserRefer uint
}

// mdb.AutoMigrate(User{}, CreditCard{})

7.3外键关联

  • GORM通常使用所有者的主键作为外键的值,例如, User的ID,
  • 当您将信用卡分配给用户时,GORM将保存用户的信用卡 ID 放入信用卡’ UserID 字段中。
  • 您可以使用标签进行更改 references

type User struct {
    ID           uint         `gorm:"primarykey"`
    MemberNumber string       `gorm:"type:varchar(255);unique"` //一对多关联需要唯一所以这里设置unique约束,否则mysql创建报错
    // 默认CreditCard会使用User表的Id作为外键,references:MemberNumber 指定使用 MemberNumber 作为外键关联
    CreditCards  []CreditCard `gorm:"foreignkey:UserMember;references:MemberNumber"`
}

type CreditCard struct {
    ID         uint `gorm:"primarykey"`
    Number     string
    UserMember string
}

// mdb.AutoMigrate(User{}, CreditCard{})

7.4一对多查询DEMO

package main

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

/*
	constraint:OnUpdate:CASCADE 【当User表更新,也会同步给CreditCards】 // 外键约束
	OnDelete:SET NULL 【当User中数据被删除时,CreditCard关联设置为 NULL,不删除记录】
*/

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

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

func main() {
	dsn := "root:123456@tcp(127.0.0.1:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"

	//0.建立连接
	mdb, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic(err)
	}

	//3.自带创建表,AutoMigrate没有建立映射关系
	// 用于项目初始化自动创建表,已存在不会重复创建
	mdb.AutoMigrate(User{}, CreditCard{})

	// 1、创建一对多
	user := User{
		Username: "zhangsan",
		CreditCards: []CreditCard{
			{Number: "0001"},
			{Number: "0002"},
		},
	}
	mdb.Create(&user)

	// 2、为已存在用户添加信用卡
	u := User{Username: "zhangsan"}
	mdb.First(&u)
	//fmt.Println(u.Username)
	mdb.Model(&u).Association("CreditCards").Append([]CreditCard{
		{Number: "0003"},
	})
}
mysql> select * from users;
+----+-------------------------+-------------------------+------------+----------+
| id | created_at              | updated_at              | deleted_at | username |
+----+-------------------------+-------------------------+------------+----------+
|  1 | 2024-10-06 04:43:29.747 | 2024-10-06 04:43:29.758 | NULL       | zhangsan |
+----+-------------------------+-------------------------+------------+----------+
1 row in set (0.00 sec)

mysql> select * from credit_cards;
+----+-------------------------+-------------------------+------------+--------+---------+
| id | created_at              | updated_at              | deleted_at | number | user_id |
+----+-------------------------+-------------------------+------------+--------+---------+
|  1 | 2024-10-06 04:43:29.749 | 2024-10-06 04:43:29.749 | NULL       | 0001   |       1 |
|  2 | 2024-10-06 04:43:29.749 | 2024-10-06 04:43:29.749 | NULL       | 0002   |       1 |
|  3 | 2024-10-06 04:43:29.760 | 2024-10-06 04:43:29.760 | NULL       | 0003   |       1 |
+----+-------------------------+-------------------------+------------+--------+---------+
3 rows in set (0.00 sec)

一对多 Association(不推荐) https://gorm.io/zh_CN/docs/associations.html

  • 使用 Association 方法, 需要把 User 查询好, 然后根据 User 定义中指定的 AssociationForeignKey 去查找 CreditCard。
package main

import (
	"encoding/json"
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

/*
	constraint:OnUpdate:CASCADE 【当User表更新,也会同步给CreditCards】 // 外键约束
	OnDelete:SET NULL 【当User中数据被删除时,CreditCard关联设置为 NULL,不删除记录】
*/

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

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

func main() {
	dsn := "root:123456@tcp(127.0.0.1:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"

	//0.建立连接
	mdb, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic(err)
	}

	//3.自带创建表,AutoMigrate没有建立映射关系
	// 用于项目初始化自动创建表,已存在不会重复创建
	mdb.AutoMigrate(User{}, CreditCard{})

	// 1、查找 用户名为 zhangsan 的所有信用卡信息
	u := User{Username: "zhangsan"} // Association必须要先查出User才能关联查询对应的CreditCard
	mdb.First(&u)
	err = mdb.Model(&u).Association("CreditCards").Find(&u.CreditCards)
	if err != nil {
		fmt.Println(err)
	}
	strUser, _ := json.Marshal(&u)
	fmt.Println(string(strUser))
}
fei@feideMBP 04mode % go run main.go
{
	"ID": 1,
	"CreatedAt": "2024-10-06T04:43:29.747+08:00",
	"UpdatedAt": "2024-10-06T04:43:29.758+08:00",
	"DeletedAt": null,
	"username": "zhangsan",
	"CreditCards": [{
		"ID": 1,
		"CreatedAt": "2024-10-06T04:43:29.749+08:00",
		"UpdatedAt": "2024-10-06T04:43:29.749+08:00",
		"DeletedAt": null,
		"Number": "0001",
		"UserID": 1
	}
					...
				   ]
}

一对多 Preload https://gorm.io/zh_CN/docs/preload.html

  • 使用 Preload 方法, 在查询 User 时先去获取 CreditCard 的记录。
package main

import (
    "encoding/json"
    "fmt"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

/*
    constraint:OnUpdate:CASCADE 【当User表更新,也会同步给CreditCards】 // 外键约束
    OnDelete:SET NULL 【当User中数据被删除时,CreditCard关联设置为 NULL,不删除记录】
*/

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

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

func main() {
    dsn := "root:123456@tcp(127.0.0.1:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"

    //0.建立连接
    mdb, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic(err)
    }

    //3.自带创建表,AutoMigrate没有建立映射关系
    // 用于项目初始化自动创建表,已存在不会重复创建
    mdb.AutoMigrate(User{}, CreditCard{})

    // 1、预加载: 查找 user 时预加载相关 CreditCards
    users := []User{}
    mdb.Preload("CreditCards").Find(&users)
    strUser, _ := json.Marshal(&users)
    fmt.Println(string(strUser))
}
[{
    "ID": 1,
    "CreatedAt": "2024-10-06T04:43:29.747+08:00",
    "UpdatedAt": "2024-10-06T04:43:29.758+08:00",
    "DeletedAt": null,
    "username": "zhangsan",
    "CreditCards": [{
        "ID": 1,
        "CreatedAt": "2024-10-06T04:43:29.749+08:00",
        "UpdatedAt": "2024-10-06T04:43:29.749+08:00",
        "DeletedAt": null,
        "Number": "0001",
        "UserID": 1
    }, 
                    ...
                   ]
}]

目的:

  • Preload: 用于预加载关联数据,通常在查询时使用。
  • Association: 用于管理已经加载模型的关联数据,包括增删改查。

使用场景:

  • 当你需要一次性加载主模型及其关联数据时,使用 Preload。
  • 当你需要对已经加载的模型的关联进行进一步操作时,使用 Association。

8.多对多

8.1many to many介绍

https://gorm.io/zh_CN/docs/many_to_many.html

  • Many to Many 会在两个 model 中添加一张连接表。
  • 例如,您的应用包含了 user 和 language,且一个 user 可以说多种 language,多个 user 也可以说一种 language。
  • 当使用 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
}

8.2反向连接

// User 拥有并属于多种 language,`user_languages` 是连接表
/*
gorm:"many2many: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;"`
}

8.3重写外键

  • 对于 many2many 关系,连接表会同时拥有两个模型的外键
type User struct {
  gorm.Model
  Languages []Language `gorm:"many2many:user_languages;"`
}

type Language struct {
  gorm.Model
  Name string
}

// 连接表:user_languages
//   外键字段user_id,引用了users表的id字段
//   foreign key: user_id, reference: users.id
//   foreign key: language_id, reference: languages.id
  • 若要重写它们,可以使用标签 foreignKey、references、joinforeignKey、joinReferences。
  • 当然,您不需要使用全部的标签,你可以仅使用其中的一个重写部分的外键、引用。

type User struct {
    gorm.Model
    //foreignKey:Refer 指定本地表字段名,joinForeignKey:UserReferID 指定关联表字段名
    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

8.4自定义第三张表

https://gorm.io/zh_CN/docs/many_to_many.html#自定义连接表

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

type Language struct {
	gorm.Model
	Name  string
	Users []*User `gorm:"many2many:user_languages"`
}

type UserLanguage struct {
	UserID     int `gorm:"primaryKey;"`
	LanguageID int `gorm:"primaryKey;"`
	CreatedAt  time.Time
	DeletedAt  gorm.DeletedAt
}

//mdb.AutoMigrate(User{}, Language{}, UserLanguage{})

  • 此时创建出的表经过测试没有外键约束

debug

2024/10/06 17:51:36 /Volumes/data/go/src/test/many2many/main.go:43
[37.939ms] [rows:0] CREATE TABLE `users` (`id` bigint unsigned AUTO_INCREMENT,`created_at` datetime(3) NULL,`updated_at` datetime(3) NULL,`deleted_at` datetime(3) NULL,PRIMARY KEY (`id`),INDEX `idx_users_deleted_at` (`deleted_at`))

2024/10/06 17:51:36 /Volumes/data/go/src/test/many2many/main.go:43
[35.273ms] [rows:0] CREATE TABLE `user_languages` (`user_id` bigint,`language_id` bigint,`created_at` datetime(3) NULL,`deleted_at` datetime(3) NULL,PRIMARY KEY (`user_id`,`language_id`))

2024/10/06 17:51:37 /Volumes/data/go/src/test/many2many/main.go:43
[37.921ms] [rows:0] CREATE TABLE `languages` (`id` bigint unsigned AUTO_INCREMENT,`created_at` datetime(3) NULL,`updated_at` datetime(3) NULL,`deleted_at` datetime(3) NULL,`name` longtext,PRIMARY KEY (`id`),INDEX `idx_languages_deleted_at` (`deleted_at`))

8.5多对多查询DEMO

package main

import (
	"encoding/json"
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"time"
)

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

type Language struct {
	gorm.Model
	Name  string
	Users []*User `gorm:"many2many:user_languages"`
}

type UserLanguage struct {
	UserID     int `gorm:"primaryKey;"`
	LanguageID int `gorm:"primaryKey;"`
	CreatedAt  time.Time
	DeletedAt  gorm.DeletedAt
}

func main() {
	dsn := "root:123456@tcp(127.0.0.1:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"

	gormConfig := &gorm.Config{
		Logger: logger.Default.LogMode(logger.Info), // 启用详细日志
	}

	//0.建立连接
	mdb, err := gorm.Open(mysql.Open(dsn), gormConfig)
	if err != nil {
		panic(err)
	}

	//3.自带创建表,AutoMigrate没有建立映射关系
	// 用于项目初始化自动创建表,已存在不会重复创建
	mdb.AutoMigrate(User{}, Language{}, UserLanguage{})

	//添加数据

	user := &User{
		Model: gorm.Model{},
		Languages: []*Language{
			{Name: "vue"},
			{Name: "golang"},
		},
	}

	mdb.Create(user)
	/*
	 INSERT INTO `languages` (`created_at`,`updated_at`,`deleted_at`,`name`) VALUES ('2024-10-06 18:07:06.874','2024-10-06 18:07:06.874',NULL,'vue'),('2024-10-06 18:07:06.874','2024-10-06 18:07:06.874',NULL,'golang') ON DUPLICATE KEY UPDATE `id`=`id`
	*/

	//Preload获取数据
	users := []*User{}
	mdb.Preload("Languages").Find(&users)
	userBytes, _ := json.Marshal(users)
	fmt.Println(string(userBytes))
	/*
		input:
			SELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL

		output:
			[{"ID":1,"CreatedAt":"2024-10-06T18:07:06.871+08:00","UpdatedAt":"2024-10-06T18:07:06.871+08:00","DeletedAt":null,"Languages":[{"ID":1,"CreatedAt":"2024-10-06T18:07:06.874+08:00","UpdatedAt":"2024-10-06T18:07:06.874+08:00","DeletedAt":null,"Name":"vue","Users":null},{"ID":2,"CreatedAt":"2024-10-06T18:07:06.874+08:00","UpdatedAt":"2024-10-06T18:07:06.874+08:00","DeletedAt":null,"Name":"golang","Users":null}]}]
	*/

	//Preload获取单条数据
	user = &User{
		Model: gorm.Model{
			ID: 1,
		},
	}
	mdb.Preload("Languages").Find(&user)
	userByte, _ := json.Marshal(user)
	fmt.Println(string(userByte))
	/*
		input:
			SELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL AND `users`.`id` = 1
		output:
			{"ID":1,"CreatedAt":"2024-10-06T18:07:06.871+08:00","UpdatedAt":"2024-10-06T18:07:06.871+08:00","DeletedAt":null,"Languages":[{"ID":1,"CreatedAt":"2024-10-06T18:07:06.874+08:00","UpdatedAt":"2024-10-06T18:07:06.874+08:00","DeletedAt":null,"Name":"vue","Users":null},{"ID":2,"CreatedAt":"2024-10-06T18:07:06.874+08:00","UpdatedAt":"2024-10-06T18:07:06.874+08:00","DeletedAt":null,"Name":"golang","Users":null}]}
	*/

	user = &User{
		Model: gorm.Model{
			ID: 1,
		},
	}
	mdb.Model(&user).Association("Languages").Find(&user.Languages)
	userAssByte, _ := json.Marshal(user)
	fmt.Println(string(userAssByte))
	/*
		input:
			SELECT `languages`.`id`,`languages`.`created_at`,`languages`.`updated_at`,`languages`.`deleted_at`,`languages`.`name` FROM `languages` JOIN `user_languages` ON `user_languages`.`language_id` = `languages`.`id` AND `user_languages`.`user_id` = 1 WHERE `languages`.`deleted_at` IS NULL
		output:
			{"ID":1,"CreatedAt":"0001-01-01T00:00:00Z","UpdatedAt":"0001-01-01T00:00:00Z","DeletedAt":null,"Languages":[{"ID":1,"CreatedAt":"2024-10-06T18:07:06.874+08:00","UpdatedAt":"2024-10-06T18:07:06.874+08:00","DeletedAt":null,"Name":"vue","Users":null},{"ID":2,"CreatedAt":"2024-10-06T18:07:06.874+08:00","UpdatedAt":"2024-10-06T18:07:06.874+08:00","DeletedAt":null,"Name":"golang","Users":null}]}
	*/

	/*
	解析:
	Preload:
		mdb.Preload("Languages"): 此方法用于指定在查找用户时,同时加载与用户相关联的 Languages 数据。
		.Find(&user): 此方法从数据库中检索用户记录,并将结果填充到 user 变量中。
	
	Association:
		mdb.Model(&user): 这里的 mdb 表示 GORM 数据库连接实例,而 Model(&user) 指定了要操作的模型,即 user。
		.Association("Languages"): 此方法用于访问与 user 关联的 Languages 集合。GORM 会根据模型中的定义自动处理这种关系。
		.Find(&user.Languages): 这个调用会将与该用户相关的所有语言记录加载到 user.Languages 切片中。
	*/
}

Last updated 06 Oct 2024, 19:17 +0800 . history