gin gorm gormV2

一、gorm介绍

参考地址: https://gorm.io/zh_CN/docs/index.html

安装包

go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite

二、连接到数据库

1、gorm 连接mysql数据库

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

数据库连接

**注意:**想要正确的处理 time.Time ,您需要带上 parseTime 参数, (更多参数) 要支持完整的 UTF-8 编码,您需要将 charset=utf8 更改为 charset=utf8mb4 查看 此文章 获取详情

db, err := gorm.Open(mysql.New(mysql.Config{
		DSN: "root:ts123456@tcp(127.0.0.1:3306)/my_gormv2?charset=utf8mb4&parseTime=True&loc=Local",
    DefaultStringSize: 256, // string 类型字段的默认长度
	}), &gorm.Config{
		// https://gorm.io/zh_CN/docs/gorm_config.html
		SkipDefaultTransaction: false, // 跳过默认事务, GORM 会在事务里执行写入操作(创建、更新、删除)。如果没有这方面的要求,您可以在初始化时禁用它
		NamingStrategy: schema.NamingStrategy{ // 表名命名策略
			TablePrefix: "t_",   // 表名前缀,`User`表为`t_users`
			SingularTable: true, // 使用单数表名,启用该选项后,`User` 表将是`t_user`
		},
		DryRun: false, // 生成 SQL 但不执行,可以用于准备或测试生成的 SQL
		DisableForeignKeyConstraintWhenMigrating: true, // 在 AutoMigrate 或 CreateTable 时,GORM 会自动创建外键约束,若要禁用该特性,可将其设置为 true
	})

链接池

https://gorm.io/zh_CN/docs/connecting_to_the_database.html#%E8%BF%9E%E6%8E%A5%E6%B1%A0

	// GORM 使用 database/sql 维护连接池
	sqlDB, _ := db.DB()
	sqlDB.SetMaxIdleConns(10) 	// SetMaxIdleConns 设置空闲连接池中连接的最大数量
	sqlDB.SetMaxOpenConns(100)	 // SetMaxOpenConns 设置打开数据库连接的最大数量
	sqlDB.SetConnMaxLifetime(time.Hour)	 // SetConnMaxLifetime 设置了连接可复用的最大时间

完整内容


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

// 定义全局变量
var GLOBAL_DB *gorm.DB

func main() {
	db, _ := gorm.Open(mysql.New(mysql.Config{
		DSN: "root:ts123456@tcp(127.0.0.1:3306)/my_gormv2?charset=utf8mb4&parseTime=True&loc=Local",
	}), &gorm.Config{
		// https://gorm.io/zh_CN/docs/gorm_config.html
		SkipDefaultTransaction: false, // 跳过默认事务, GORM 会在事务里执行写入操作(创建、更新、删除)。如果没有这方面的要求,您可以在初始化时禁用它
		NamingStrategy: schema.NamingStrategy{ // 表名命名策略
			//TablePrefix: "t_",   // 表名前缀,`User`表为`t_users`
			SingularTable: true, // 使用单数表名,启用该选项后,`User` 表将是`t_user`
		},
		DryRun:                                   false, // 生成 SQL 但不执行,可以用于准备或测试生成的 SQL
		DisableForeignKeyConstraintWhenMigrating: true,  // 在 AutoMigrate 或 CreateTable 时,GORM 会自动创建外键约束,若要禁用该特性,可将其设置为 true
	})

	// GORM 使用 database/sql 维护连接池
	sqlDB, _ := db.DB()
	sqlDB.SetMaxIdleConns(10)           // SetMaxIdleConns 设置空闲连接池中连接的最大数量
	sqlDB.SetMaxOpenConns(100)          // SetMaxOpenConns 设置打开数据库连接的最大数量
	sqlDB.SetConnMaxLifetime(time.Hour) // SetConnMaxLifetime 设置了连接可复用的最大时间

  GLOBAL_DB = db // 把 db 赋值
	// https://gorm.io/zh_CN/docs/migration.html
	// AutoMigrate 用于自动迁移表
	//db.AutoMigrate(&User{})

	// 如果存在表则删除(删除时会忽略、删除外键约束)
	//M.DropTable(&User{})

}


表迁移

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

type User struct {
		Name string
		Age int
	}

	// https://gorm.io/zh_CN/docs/migration.html
	// AutoMigrate 用于自动迁移表
	//db.AutoMigrate(&User{})

	// 迁移表
	M := db.Migrator()
	M.CreateTable(&User{}) // 创建表

	// 如果存在表则删除(删除时会忽略、删除外键约束)
	//M.DropTable(&User{})

	// 检查 `User` 对应的表是否存在
	fmt.Println(M.HasTable(&User{}))
2、模型

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

模型是标准的 struct,由 Go 的基本数据类型、实现了 Scanner 和 Valuer 接口的自定义类型及其指针或别名组成

GORM 倾向于约定,而不是配置。默认情况下,GORM 使用 ID 作为主键,使用结构体名的 蛇形复数 作为表名,字段名的 蛇形 作为列名,并使用 CreatedAtUpdatedAt 字段追踪创建、更新时间

  1. GORM 定义一个 gorm.Model 结构体

    // gorm.Model 的定义
    type Model struct {
      UUID        uint           `gorm:"primaryKey"`
      CreatedAt time.Time
      UpdatedAt time.Time
      DeletedAt gorm.DeletedAt `gorm:"index"`
    }
    
  2. 将它嵌入到结构体中

    type TestUser struct {
    	Model        Model `gorm:"embedded"`
    	Name         string
    	Email        *string
    	Age          uint8
    	Birthday     *time.Time
    	MemberNumber sql.NullString
    	ActivatedAt  sql.NullTime
    }
    
  3. 字段标签

    声明model时,tag 是可选的

    • column : 列名称

    • primaryKey : 列为主键

    • unique : 列为唯一

    • default : 指定列的默认值

    • not null : 指定列不允许为空

    • embedded : 嵌套字段

    • embeddedPrefix:嵌套字段的列名前缀

    • index : 指定索引

    • uniqeIndex : 唯一索引

    • comment : 注释,数据库字段描述

    • precision : 精度(浮点型)

    • size : 列大小

    示例

    type TestUser struct {
    	Model        Model   `gorm:"embedded"`
    	Name         string  `gorm:"comment:姓名"`
    	Email        *string `gorm:"column:my_email;comment:邮箱"`
    
    }
    

三、gorm简单增删改查

1、创建数据

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

创建数据

	user := TestUser{Name: "vic", Age: 20}

	result := GLOBAL_DB.Create(&user) // 通过指针来创建数据
	// 返回 error, 影响的条数
	fmt.Println(result.Error, "=======", result.RowsAffected)

指定字段创建数据

	user := TestUser{Name: "vic", Age: 22}

	// 用  Select 指定的字段创建
	result := GLOBAL_DB.Select("age").Create(&user) // 通过指针来创建数据

	// 返回 error, 影响的条数
	fmt.Println(result.Error, "=======", result.RowsAffected)

创建一个记录且一同忽略传递要略去的字段值

	user := TestUser{Name: "ccg", Age: 22}

	// 用  Omit 指定的字段创建
	result := GLOBAL_DB.Omit("age").Create(&user) // 通过指针来创建数据

	// 返回 error, 影响的条数
	fmt.Println(result.Error, "=======", result.RowsAffected)

批量插入数据

利用数组,将一个 slice 传递给 Create 方法

users := []TestUser{
		{Name: "C1", Age: 22},
		{Name: "C2", Age: 22},
		{Name: "C3", Age: 22},
	}

	// 批量插入数据
	result := GLOBAL_DB.Create(&users) // 通过指针来创建数据

	// 返回 error, 影响的条数
	fmt.Println(result.Error, "=======", result.RowsAffected)
2、查询数据

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

检索单个对象

	// 获取第一条记录(主键升序),
	// 方式一: 目标 struct 是指针
	var ret1 TestUser
	GLOBAL_DB.First(&ret1)
	fmt.Println(ret1)

	// 方式二:通过 `db.Model()` 指定了 model
	ret2 := map[string]interface{}{}
	GLOBAL_DB.Model(&TestUser{}).First(&ret2)
	fmt.Println(ret2)

用主键检索

// 主键检索数据
	// 主键是数字类型
	//SELECT * FROM users WHERE uuid = 5;
	var user1 TestUser
	GLOBAL_DB.First(&user1, 5)
	fmt.Println(user1)

	// 把返回的数据拿出来,增加判断
	var User TestUser
	dbRes := GLOBAL_DB.First(&User, 5)
	fmt.Println(dbRes.Error)  // 返回结果 record not found
	fmt.Println(errors.Is(dbRes.Error,gorm.ErrRecordNotFound)) // dbRes.Error 查询返回的错误,gorm.ErrRecordNotFound 系统gorm 返回的错误,结果返回 true;通过判断查询是否成功,然后再进行下一步操作
	
	
	//主键是字符串
	var user2 TestUser
	GLOBAL_DB.First(&user2, "name = ?", "C2")
	fmt.Println(user2)
	

检索全部数据

// 检索全部数据, 使用切片接收返回结果
	var users []TestUser
	GLOBAL_DB.Find(&users)
	fmt.Println(users)

String条件

// 获取第一条匹配的记录
	var user TestUser
	// 忽略不想显示的字段
	GLOBAL_DB.Omit("created_at", "updated_at").Where("name = ?", "vic").First(&user)
	fmt.Println(user)

	// 获取 LIKE 匹配的所有记录
  // SELECT * FROM users WHERE name LIKE '%g%';
	var users []TestUser
	GLOBAL_DB.Where("name LIKE ?", "%g%").Find(&users)
	fmt.Println("LIKE: ", users)

	// AND
	var U1 []TestUser
	GLOBAL_DB.Where("name = ? AND age >= ?", "ccg", "21").Find(&U1)
	fmt.Println("AND: ", U1)

Struct & Map

	// Struct
	// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1;
	var user TestUser
	GLOBAL_DB.Where(&TestUser{Name: "ccg", Age: 21}).First(&user)
	fmt.Println("Struct: ", user)

	// Map
	// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;
	users := map[string]interface{}{}
	GLOBAL_DB.Model(&TestUser{}).Where(map[string]interface{}{"name": "B2", "age": 23}).Find(&users)
	fmt.Println("Map: ", users)
3、更新数据

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

Update 更新单列

  // Update 更新单个列
	// 当使用了 Model 方法,且该对象主键有值
	GLOBAL_DB.Model(&TestUser{}).Where("age = ?", 21).Update("name", "A2")


	// 根据条件和 model 的值进行更新
	var users TestUser
	GLOBAL_DB.Model(&users).Where("name = ?", "vic").Update("age", 18)

Save 保存所有字段

	// Save 会保存所有的字段,即使字段是零值
	// 更新查询的第一条数据
	var user TestUser
	GLOBAL_DB.First(&user)
	user.Name = "CC"
	user.Age = 100
	GLOBAL_DB.Save(&user)


	// 批量更新符合条件的列
	var users []TestUser
	dbRes := GLOBAL_DB.Where("name LIKE ?", "%2%").Find(&users)
	for k := range users {
		users[k].Age = 30
	}
	dbRes.Save(&users)

Updates 更新所有字段 — 常用

// Updates 更新所有字段 struct 和 map[string]interface{} 参数,

	// Struct 更新时,GORM 只会更新非零字段
	var user TestUser
	GLOBAL_DB.First(&user).Updates(TestUser{Name: "AA", Age: 222})

	// 如果您想确保指定字段被更新,你应该使用 Select 更新选定字段,或使用 map 来完成更新操作
	GLOBAL_DB.First(&user).Updates(map[string]interface{}{"name": "A5", "age": 222})


	// 批量更新
	// Struct
	GLOBAL_DB.Where(&TestUser{}).Where("name = ?", "A2").Updates(TestUser{Age: 55, Address: "北京"})

	//	根据 map 更新
	GLOBAL_DB.Table("test_user").Where("age IN ?", []int{22, 30}).Updates(map[string]interface{}{"address": "上海"})
4、删除数据

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

// 如果您的模型包含了一个 gorm.DeletedAt 字段(gorm.Model 已经包含了该字段),它将自动获得软删除的能力!

	// 拥有软删除能力的模型调用 Delete 时,记录不会被从数据库中真正删除。但 GORM 会将 DeletedAt 置为当前时间, 并且你不能再通过正常的查询方法找到该记录。

	// 根据主键删除,可以使用数字,字符串
	GLOBAL_DB.Delete(&TestUser{}, 2)

	GLOBAL_DB.Delete(&TestUser{}, "5")

	GLOBAL_DB.Delete(&TestUser{}, []int{1, 3, 4})


	// 批量删除
	// 如果指定的值不包括主属性,那么 GORM 会执行批量删除
	GLOBAL_DB.Where("name LIKE ?", "%g%").Delete(TestUser{})

	// 另一种写法
	GLOBAL_DB.Where(TestUser{}, "name LIKE ?", "%g%").Delete(TestUser{})



	// 永久删除
	GLOBAL_DB.Unscoped().Delete(&TestUser{}, 10)
5、原生SQL
// https://gorm.io/zh_CN/docs/sql_builder.html#%E5%8E%9F%E7%94%9F-SQL
type Result struct {
  ID   int
  Name string
  Age  int
}

var result Result
db.Raw("SELECT id, name, age FROM users WHERE name = ?", 3).Scan(&result)

db.Raw("SELECT id, name, age FROM users WHERE name = ?", 3).Scan(&result)

var age int
db.Raw("SELECT SUM(age) FROM users WHERE role = ?", "admin").Scan(&age)

var users []User
db.Raw("UPDATE users SET name = ? WHERE age = ? RETURNING id, name", "jinzhu", 20).Scan(&users)

四、数据关联

1、Belongs to

​ belongs to 创建的每一个实例都 “属于” 另一个模型的一个实例

示例结构体 https://gorm.io/zh_CN/docs/belongs_to.html

// User 属于 Company,CompanyID 是关联外键,并且每个 User 能且只能被分配一个 Company,
type User struct { // 用户
	gorm.Model
	Name      string `gorm:"unique"`
	CompanyID uint
	Company   Company
}

type Company struct { // 公司
	gorm.Model
	Name string
}

基本操作

// 创建数据库, 自动创建属于表 Company
	//GLOBAL_DB.AutoMigrate(&User{})

	// 创建数据
	// 创建两家 Company
	C1 := Company{
		Name: "百度",
	}

	C2 := Company{
		Model: gorm.Model{ID: 2},
		Name:  "新浪",
	}

	GLOBAL_DB.Create(&C1)
	GLOBAL_DB.Create(&C2)
	

	//	创建用户
	// 根据已有的ID进行管理
	U1 := User{
		Name:      "Vic",
		CompanyID: 1,
	}
	GLOBAL_DB.Create(&U1)
	

	//	创建数据,自动关联
	C3 := Company{
		Name: "拼多多",
	}
	U2 := User{
		Name:    "ccg",
		Company: C3,
	}
	GLOBAL_DB.Create(&U2)


	// 查询
	var company Company
	GLOBAL_DB.First(&company, 2)
	fmt.Println("查询:", company)
	

预加载 https://gorm.io/zh_CN/docs/preload.html

​ GORM 运行通过 Preload 或者 Joins 来主动加载实体的关联关系

	  //	Preload 预加载
		var puser User
		GLOBAL_DB.Preload("Company").First(&puser, 1)
		fmt.Println("Preload预加载查询:", puser)


		//	Jions
		var juser User
		GLOBAL_DB.Joins("Company").First(&juser, 2)
		fmt.Println("Joins预加载查询:", juser)

Belongs to 的 CURD 使用关联模式进行操作

https://gorm.io/zh_CN/docs/associations.html#%E5%85%B3%E8%81%94%E6%A8%A1%E5%BC%8F

//	关联模式
	/*
		 判断是否下面的二个要求,符合会开始关联模式,否则返回错误
			1. user 是源模型,主键不能为空,
			2. 关系的字段名称是 Company
	*/

	var user User
	err := GLOBAL_DB.Model(&user).Association("Company").Error
	if err != nil {
		fmt.Println("该数据没有关联:", err)
	}

	//	添加关联, 前提表中数据已经存在
	
		u1 := User{
			Model: Model{UUID: 3},
		}
		c1 := Company{Model: Model{UUID: 4}}
		GLOBAL_DB.Model(&u1).Association("Company").Append(&c1)
	


	// 替换关联
	u2 := User{
		Model: Model{
			UUID: 2,
		},
	}

	c4 := Company{
		Model: Model{
			UUID: 4,
		},
	}
	c7 := Company{
		Model: Model{
			UUID: 7,
		},
	}

	GLOBAL_DB.Model(&u2).Association("Company").Replace(&c4, &c7)


	// 删除源模型与关联之间的所有引用
	u1 := User{
		Model: Model{
			UUID: 1,
		},
	}
	GLOBAL_DB.Model(&u1).Association("Company").Clear()

2、Has One

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

基础数据

// Has one 与另外一个模型建立一对一的关联,这种关联表明一个模型的每个实例都包涵或拥有另一个模型的实例
// 每个 User1 只能有一张 Card ,User1 有一张 Caed,UserID 是外键
type User1 struct {
	Model
	Name string
	Card Card
}

type Card struct {
	Model
	Number  string
	User1ID uint
}

添加数据

执行预加载

//创建数据库
	//GLOBAL_DB.AutoMigrate(&User1{}, &Card{})

	// 添加数据
	/*
		u11 := User1{
			Name: "u11",
		}
		u22 := User1{
			Name: "u22",
		}

		c11 := Card{
			Number:  "11111",
			User1ID: 1,
		}

		c22 := Card{
			Number:  "2222",
			User1ID: 2,
		}
		GLOBAL_DB.Create(&u11)
		GLOBAL_DB.Create(&u22)
		GLOBAL_DB.Create(&c11)
		GLOBAL_DB.Create(&c22)
	*/

	// 查询
	var card Card
	GLOBAL_DB.First(&card)
	fmt.Println(card)

	// 预加载
	// Preload 预加载
	var puser1 User1
	GLOBAL_DB.Preload("Card").Find(&puser1, 2)
	fmt.Println("Preload预加载查询:", puser1)

	// Jions 预加载
	var juser1 User1
	GLOBAL_DB.Joins("Card").Find(&juser1, 1)
	fmt.Println("Joins预加载查询:", juser1)

Has one 的 CURD 使用关联模式进行操作

// 添加关联,前提数据已经在数据库存在
	c33 := Card{
		Model: Model{
			UUID: 3,
		},
	}

	u33 := User1{
		Model: Model{
			UUID: 3,
		},
	}

	// 添加关联
	GLOBAL_DB.Model(&u33).Association("Card").Append(&c33)

	// 替换关联
	u11 := User1{
		Model: Model{
			UUID: 1,
		},
	}

	c11 := Card{
		Model: Model{
			UUID: 1,
		},
	}

	c22 := Card{
		Model: Model{
			UUID: 2,
		},
	}
	GLOBAL_DB.Model(&u11).Association("Card").Replace(&c11, &c22)

	// 删除关联
	GLOBAL_DB.Model(&u11).Association("Card").Delete(&u11)

	// 清空关联
	GLOBAL_DB.Model(&u11).Association("Card").Clear()
3、Has Many

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

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

创建模型

// 声明 teacher 和 class 模型,且每个 class 可以有多个 teacher

type Class struct { // 班级
	Model
	Name     string
	Teachers []Teacher
}
type Teacher struct { // 教师
	Model
	name    string
	ClassID uint
}


	// 创建数据库,
	//GLOBAL_DB.AutoMigrate(&Class{}, &Teacher{})

添加数据

	// 添加数据
	// 创建二个 Teacher
	T1 := Teacher{
		Name: "英语老师",
	}
	T2 := Teacher{
		Name: "法语老师",
	}

	// 创建一个 class, 里面包涵二个 teacher
	C1 := Class{
		Name:     "初中一年级",
		Teachers: []Teacher{T1, T2},
	}

	//使用Create 方法创建
	GLOBAL_DB.Create(&C1)

数据查询

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

	// 查询, 只查询到 class,没有查询到包涵的 teacher
	var class Class
	GLOBAL_DB.First(&class)
	fmt.Println(class)

 	// 使用 Preload 预加载的模式, 查询关联的 teacher
	var class Class
	GLOBAL_DB.Preload("Teachers").First(&class)
	fmt.Println(class)


	// Grom 运行使用带条件的Preload, 查询包涵 英语老师
	var class Class
	GLOBAL_DB.Preload("Teachers", "name = ?", "英语老师").First(&class)
	fmt.Println(class)

	// 可以通过 func(db *gorm.DB) *gorm.DB 实现自定义预加载 SQL
	var class Class
	GLOBAL_DB.Preload("Teachers", func(db *gorm.DB) *gorm.DB {
		return db.Where("name = ?", "法语老师")
	}).First(&class)
	fmt.Println(class)

嵌套预加载

https://gorm.io/zh_CN/docs/preload.html#%E5%B5%8C%E5%A5%97%E9%A2%84%E5%8A%A0%E8%BD%BD

// 增加 Info 模型,和 Teacher 属于 一对一关系
type Info struct {
	Model
	Money     int
	TeacherID uint
}

type Teacher struct { // 教师
	Model
	Name    string
	ClassID uint
	Info    Info
}

查询数据

		var class Class
		GLOBAL_DB.Preload("Teachers.Info").First(&class)
		fmt.Println(class)

		// 只查询包涵 英语老师
		var class Class
		GLOBAL_DB.Preload("Teachers.Info").Preload("Teachers", "name = ?", "英语老师").First(&class)
		fmt.Println(class)

// 注意:Preload 带条件的预加载查询,只适用于查询当前的预加载语句,如果查询 money > 200 的结果就会报错,返回所有
	var class Class
		GLOBAL_DB.Preload("Teachers.Info", "money > 200").Preload("Teachers").First(&class)
		fmt.Println(class)

使用joins 关联查询解决上述问题

joins Preload 适用于一对一的关系,例如 has one,belongs to

	// 使用 joins Preload 查询数据
	var class Class
	GLOBAL_DB.Preload("Teachers", func(db *gorm.DB) *gorm.DB {
		return db.Joins("Info")
	}).First(&class)
	fmt.Println(class)	


	// 使用 joins 查询实现 money > 200 的 teacher
	var class Class
	GLOBAL_DB.Preload("Teachers", func(db *gorm.DB) *gorm.DB {
		return db.Joins("Info").Where("money > 200")
	}).First(&class)
	fmt.Println(class)
4、Many To Many

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

many to Many 会在二个 model 中添加一张链接表

Model 模型

// 创建模型 user 和 address ,一个 user 可以包含多个address; 多个user可以属于一种 address,user包含 info,has one 关系

type InfoR struct {
	Model
	Money    int
	PeopleID uint
}

type People struct {
	Model
	Name      string
	InfoR     InfoR
	Addresses []Address `gorm:"many2many:people_address;"`
}

type Address struct {
	Model
	Name    string
	Peoples []People `gorm:"many2many:people_address;"`
}

GLOBAL_DB.AutoMigrate(&InfoR{}, &People{}, &Address{})

添加数据

// 添加数据 
	i1 := Infos{
		Money: 2000,
	}

	a1 := Address{
		Name: "北京市",
	}
	a2 := Address{
		Name: "上海市",
	}

	p := People{
		Name:      "王小二",
		Infos:     i1,
		Addresses: []Address{a1, a2},
	}

	// 添加数据, 指定address UUID
	i2 := Infos{
		Money: 1000,
	}

	a1 := Address{
		Model: Model{
			UUID: 1,
		},
		Name: "北京市",
	}
	a2 := Address{
		Model: Model{
			UUID: 2,
		},
		Name: "上海市",
	}

	p := People{
		Name:      "王小三",
		Infos:     i2,
		Addresses: []Address{a1, a2},
	}

	GLOBAL_DB.Create(&p)

数据查询

// 查询 UUID 为 1 的 people, 没有查询出address 和 infos
	p := People{
		Model: Model{
			UUID: 1,
		},
	}
	GLOBAL_DB.Find(&p)
	fmt.Println(p)

	// 使用关联模式查询, 只查询出来了 address
	var address []Address
	GLOBAL_DB.Model(&p).Association("Addresses").Find(&address)
	fmt.Println(address)

	// 查询出来了 address, 结果想把 people也带上,使用 Preload
	GLOBAL_DB.Model(&p).Preload("Peoples").Association("Addresses").Find(&address)
	fmt.Println(address)

	// 查询出 address people,结果把 infos结果也带着, 使用 Preload joins
	GLOBAL_DB.Model(&p).Preload("Peoples", func(db *gorm.DB) *gorm.DB {
		return db.Joins("Infos")
	}).Association("Addresses").Find(&address)
	fmt.Println(address)

	// 只带出 money > 1500
	GLOBAL_DB.Model(&p).Preload("Peoples", func(db *gorm.DB) *gorm.DB {
		return db.Joins("Infos").Where("money > 1500")
	}).Association("Addresses").Find(&address)
	fmt.Println(address)

五、多态关联

https://gorm.io/zh_CN/docs/has_many.html#%E5%A4%9A%E6%80%81%E5%85%B3%E8%81%94

Gorm 为 has onehas many 提供了多态关联支持。它会将拥有者实体的表名、主键都保存到多态类型的字段中

`gorm:"polymorphic:Owner;polymorphicValue:master"`
  • polymorphic:指定多态类型(结构体上面的那个标签名,固定写法;Owner 值对应 OwnerID(和实体者的主键类型相同)、OwnerType
  • polymorphicValue:指定多态的值,自定义字段的名称(数据库里用来记录是哪个结构体的哪个字段的值)
  • 多态的结构体的数据不可以同时被多人拥有

模型示例

// Cat 实体
type Cat struct {
	Model
	Name string
	Toy  Toy `gorm:"polymorphic:Owner;polymorphicValue:CatToy"`
}

/*
	Dog 实体,多态不可以被多个实体同时拥有,但是可以一个实体对应多个多态
	polymorphic: Owner 对应 多态的 OwnerID、OwnerType 前缀名称,类型和主键类型保持一致;
	polymorphicValue:DogToy 设置多态类型的值,一般设置成主键的名称和多态名
*/
type Dog struct {
	Model
	Name string
	Toy  []Toy `gorm:"polymorphic:Owner;polymorphicValue:DogToy"`
}

// Toy 多态实体
type Toy struct {
	Model
	Name      string
	OwnerID   uint
	OwnerType string
}

表结构

  • cat 表结构
uuid name
  • dog 表结构
uuid name
  • Toy 多态表结构
uuid name owner_id owner_type

添加数据


	GLOBAL_DB.Create(&Cat{Name: "小黑猫", Toy: Toy{Name: "小老鼠"}})
	GLOBAL_DB.Create(&Dog{Name: "小黑狗", Toy: []Toy{
		{Name: "小骨头"},
		{Name: "大骨头"},
	}})

数据库表数据

  • cat 表结构
uuid name
1 小黑猫
  • dog 表结构
uuid name
1 小黑狗
  • Toy 多态表结构
uuid name owner_id owner_type
1 小老鼠 1 CatToy
2 小骨头 1 DogToy
3 大骨头 1 DogToy

六、关联模式

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

  • foreignKey:指定当前模型的列作为连接表的外键,即指定外键以自己的哪个key去关联

  • references:指定引用表名的列名,其将被映射为连接表的外键,指定引用被关联结果的那个key

  • joinForeignKey:指定连接表的外键列名,其将被映射到当前表

  • joinReferences:指定连接表的外键列表,其将别映射到引用表

1、has many

示例代码

package main

/*
	Zoo 实体
	foreignKey:ZooName 指向连接表(Monkey)的外键的key(Monkey的ZooName列名)
	references:Name	引用本结构体的列名(Zoo的Name列名),被映射为连接表的外键名称
*/
type Zoo struct {
	Model
	Name   string
	Monkey []Monkey `gorm:"foreignKey:ZooName;references:Name"`
}

// Monkey 实体
type Monkey struct {
	Model
	Name    string
	ZooName string
}

func Foreign() {
	// 创建表
	GLOBAL_DB.AutoMigrate(&Monkey{}, &Zoo{})

	// 增加数据
	GLOBAL_DB.Create(&Zoo{
		Name: "北京动物园",
		Monkey: []Monkey{
			{Name: "小黑猴"},
			{Name: "小白猴"},
		}})
}

数据库表内容

Zoo 表

uuid name
1 北京动物园

Monkey 表

uuid name zoo_name
1 小黑猴 北京动物园
2 小白猴 北京动物园
2、Many2Many

代码示例

package main

/*
	Zoo 实体
注意: 某些数据库只允许在唯一索引字段上创建外键,如果您在迁移时会创建外键,则需要指定 unique index 标签。
	many2many:zoo_monkey 中间关联表
	foreignKey:Name 指向本表的主键列名(Zoo的Name)
	joinForeignKey:ZooName 重写中间表关联本表的列名

	references:Name	指向关联表的主键列名(Monkey 的 Name)
	joinReferences:MonkeyName 重写中间表关联的关联表的列名
*/
type Zoo struct {
	Model
	Name   string   `gorm:"primaryKey"`
	Monkey []Monkey `gorm:"many2many:zoo_monkey;foreignKey:Name;joinForeignKey:ZooName;References:Name;joinReferences:MonkeyName"`
}

// Monkey 实体
type Monkey struct {
	Model
	Name string `gorm:"primaryKey"`
}

func Foreign() {
	// 创建表
	GLOBAL_DB.AutoMigrate(&Zoo{}, &Monkey{})

	// 增加数据
	GLOBAL_DB.Create(&Zoo{
		Name: "北京动物园",
		Monkey: []Monkey{
			{Name: "小黑猴"},
			{Name: "小白猴"},
		}})
}

数据库表内容

zoo

uuid name
1 北京动物园

monkey

uuid name
1 小黑猴
2 小白猴

zoo_monkey

zoo_name monkey_name
北京动物园 小黑猴
北京动物园 小白猴

七、事务

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

为了确保数据一致性,GORM 会在事务里执行写入操作(创建、更新、删除)。如果没有这方面的要求,您可以在初始化时禁用它,这将获得大约 30%+ 性能提升。

// 全局禁用
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
  SkipDefaultTransaction: true,
})

1、基础事务
package main

import "gorm.io/gorm"

// Ts 结构体
type Ts struct {
	Model
	Name string `gorm:"unique"`
}

func Transaction() {
	// 创建表
	GLOBAL_DB.AutoMigrate(&Ts{})
  
	// 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
	GLOBAL_DB.Transaction(func(tx *gorm.DB) error {
		if err := tx.Create(&Ts{Name: "事务一"}).Error; err != nil {
			// 返回任何错误都会回滚事务,即不执行此事务
			return err
		}
		if err := tx.Create(&Ts{Name: "事务二"}).Error; err != nil {
			// 返回任何错误都会回滚事务,即不执行此事务
			return err
		}
		/*
			// 因为指定了 Name 列为名为唯一值,如果开启下面的事务,则上面的二条事务不会执行
			if err := tx.Create(&Ts{Name: "事务一"}).Error; err != nil {
				// 返回任何错误都会回滚事务,即不执行此事务
				return err
			}
		*/

		// 返回 nil 提交事务
		return nil
	})
}

2、嵌套事务
package main

import "gorm.io/gorm"

// Ts 结构体
type Ts struct {
	Model
	Name string `gorm:"unique"`
}

func Transaction() {

	GLOBAL_DB.AutoMigrate(&Ts{})
	// 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
	GLOBAL_DB.Transaction(func(tx *gorm.DB) error {
		if err := tx.Create(&Ts{Name: "事务一"}).Error; err != nil {
			// 返回任何错误都会回滚事务,即不执行此事务
			return err
		}
		// GORM 支持嵌套事务, 可以回滚较大事务内执行的一部分操作
		tx.Transaction(func(tx *gorm.DB) error {
			if err := tx.Create(&Ts{Name: "事务二"}).Error; err != nil {
				return err
			}
			// 因为指定了 Name 列为名为唯一值,如果开启下面的事务,列名重复会报错,则此嵌套里面的事务都不会执行
			err := tx.Create(&Ts{Name: "事务一"})
			if err != nil {
				return err.Error
			}
			// 返回 nil 提交事务
			return nil
		})

		// 返回 nil 提交事务
		return nil
	})
}

3、手动事务

Gorm 支持直接调用事务控制方法(commit、rollback)

package main

// Ts 结构体
type Ts struct {
	Model
	Name string `gorm:"unique"`
}

func Transaction() {

	GLOBAL_DB.AutoMigrate(&Ts{})
	// 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')

  // 开始事务
	tx := GLOBAL_DB.Begin()

	// 执行 数据库操作
	tx.Create(&Ts{Name: "事务一"})
	tx.Create(&Ts{Name: "事务二"})

	tx.Create(&Ts{Name: "事务一"})
	// 遇到错误时回滚事务; 上面Name是唯一值,出现了多次 事务一 ,所以回滚上面的事务,即不执行
	tx.Rollback()
	
  // 提交事务
	tx.Commit()

}

SavePointRollbackto

GORM 提供了 SavePointRollbackto 方法,来提供保存点以及回滚至保存点功能

tx := db.Begin()
tx.Create(&user1)

tx.SavePoint("sp1")
tx.Create(&user2)
tx.RollbackTo("sp1") // Rollback user2

tx.Commit() // Commit user1

八、自定义数据类型

GORM 提供了少量接口,使用户能够为 GORM 定义支持的数据类型

1、json示例
package main

import (
	"database/sql/driver"
	"encoding/json"
	"errors"
	"fmt"
)

// CInfo 定义一个数据类型
type CInfo struct {
	Sex string
	Age int
}

// CUser 实体类引用定义的(CInfo)类型
type CUser struct {
	Model
	Name string
	Info CInfo
}

// Value 实现 driver.Value 接口,Value 返回 json value ;存数据用
func (c CInfo) Value() (driver.Value, error) {
	// 把数据加工成 json
	str, err := json.Marshal(c)
	if err != nil {
		return nil, err
	}
	// 并返回 string 类型的数据
	return string(str), nil
}

// Scan 实现 sql.Scanner 接口, 渲染出 value 的值;查数据用
func (c *CInfo) Scan(value interface{}) error {
	// 接收value的值,利用类型断言判断值是否为 字节类型
	str, ok := value.([]byte)
	if !ok {
		return errors.New(fmt.Sprint("数据类型不匹配"))
	}
	// 断言成功,把结果渲染到 c 指针上面
	json.Unmarshal(str, c)
	return nil
}

func CustomType() {
	// 创建数据库
	GLOBAL_DB.AutoMigrate(&CUser{})

	/*
		// 添加数据
		GLOBAL_DB.Create(&CUser{
			Name: "王小二",
			Info: CInfo{
				Sex: "男",
				Age: 20,
			}})

	*/

	// 查询数据
	var c CUser
	GLOBAL_DB.First(&c)
	fmt.Println(c)
}

2、数组示例
package main

import (
	"database/sql/driver"
	"encoding/json"
	"errors"
	"fmt"
	"strings"
)

// CInfo 定义一个数据类型
type CInfo struct {
	Sex string
	Age int
}

// Args 定义数组类型
type Args []string

// CUser 实体类引用定义的(CInfo)类型
type CUser struct {
	Model
	Name    string
	Info    CInfo // 使用自定义的 CInfo 类型
	Address Args  // 使用自定义的 Args 类型
}

// Value 实现 driver.Value 接口,Value 返回 json value ;存json数据用
func (c CInfo) Value() (driver.Value, error) {
	// 把数据加工成 json
	str, err := json.Marshal(c)
	if err != nil {
		return nil, err
	}
	// 并返回 string 类型的数据
	return string(str), nil
}

// Scan 实现 sql.Scanner 接口, 渲染出 value 的值;查Json数据用
func (c *CInfo) Scan(value interface{}) error {
	// 接收value的值,利用类型断言判断值是否为 字节类型
	str, ok := value.([]byte)
	if !ok {
		return errors.New(fmt.Sprint("数据类型不匹配"))
	}
	// 断言成功,把结果渲染到 c 指针上面
	json.Unmarshal(str, c)
	return nil
}

// Value 实现 driver.Value 接口,Value 返回 json value ;存数组数据用
func (a Args) Value() (driver.Value, error) {
	// 判断数组的长度
	if len(a) > 0 {
		// 有数据,就进行数组的拼接
		var str string = a[0]
		for _, v := range a[1:] {
			// 循环拼接 str 字符串
			str += "," + v
		}

		return str, nil
	} else {
		// 没有数据就存 空
		return "", nil
	}
}

// Scan 实现 sql.Scanner 接口, 渲染出 value 的值;查 数组数据用
func (a *Args) Scan(value interface{}) error {
	// 接收value的值,利用类型断言判断值是否为 字节类型
	str, ok := value.([]byte)
	if !ok {
		return errors.New(fmt.Sprint("数据类型不匹配"))
	}
	// 断言成功,把结果渲染到 a 指针上面, 利用strings的字符串分割 str的值 返回原始的结果
	*a = strings.Split(string(str), ",")
	return nil
}

func CustomType() {
	// 创建数据库
	GLOBAL_DB.AutoMigrate(&CUser{})

	// 添加数据
	GLOBAL_DB.Create(&CUser{
		Name: "王小二",
		Info: CInfo{
			Sex: "男",
			Age: 20,
		},
		Address: Args{
			"北京市",
			"上海市",
			"杭州市",
		},
	})

	// 查询数据
	var c CUser
	GLOBAL_DB.First(&c)
	fmt.Println(c)
}

你可能感兴趣的:(gin,数据库,mysql,gorm,gormv2)