Bootstrap

gorm学习笔记(11)-gorm一对多关系,以及一对多关系中的预加载和关系操作

gorm使用原生sql功能

存在嵌套情况下的预加载全部clause.Associations方法未实现问题。

准备工作

建立数据库连接

import (
	"fmt"
	_ "github.com/go-sql-driver/mysql"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"log"
)

var db *gorm.DB

func OpenDB() {
	dsn := "root:adss123@tcp(127.0.0.1:3306)/go_db?charset=utf8mb4&parseTime=True&loc=Local"
	res, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	db = res
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("成功:%v\n", db)
}

一对多关系,HasMany

建表

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

例如,您的应用包含 user 和 credit card 模型,且每个 user 可以有多张 credit card。

// User 有多张 CreditCard,UserID 是外键
type Dogs struct {
	gorm.Model
	Name     string
	OwnersID int
}

type Owners struct {
	gorm.Model
	Name string
	Dogs []Dogs
}

以此建表

type Dogs struct {
	gorm.Model
	Name     string
	OwnersID int
}

type Owners struct {
	gorm.Model
	Name string
	Dogs []Dogs
}

func CreateHasMany() {
	OpenDB()
	db.AutoMigrate(&Owners{}, &Dogs{})
}

创建的表结构与一对一创建的表结构看上去没区别。

重写外键

要定义 has many 关系,同样必须存在外键。 默认的外键名是拥有者的类型名加上其主键字段名

例如,要定义一个属于 User 的模型,则其外键应该是 UserID。

此外,想要使用另一个字段作为外键,您可以使用 foreignKey 标签自定义它:

type User struct {
  gorm.Model
  CreditCards []CreditCard `gorm:"foreignKey:UserRefer"`
}

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

创建记录

func InsertHasMany() {
	OpenDB()
	dog1 := &Dogs{Name: "狗子1号"}
	dog2 := &Dogs{Name: "狗子2号"}
	owners := &Owners{Name: "ylj", Dogs: []Dogs{*dog1, *dog2}}
	db.Create(owners)
}

记录为
dogs
在这里插入图片描述
owners
在这里插入图片描述


简单发现,在嵌套情况下,创建最外层记录,会自动创建内层记录。


查询功能(预加载)

查询功能,与单表查询没太大区别,还是着重实验预加载功能。
未使用预加载功能:

func QueryHasMany() {
	OpenDB()
	owner := &Owners{}
	db.Model(owner).First(owner, 1)
	fmt.Println(owner)
}

在这里插入图片描述
未出现owner下属的dog信息


添加预加载功能:

func QueryHasMany() {
	OpenDB()
	owner := &Owners{}
	db.Model(owner).Preload("Dogs").First(owner, 1)
	fmt.Println(owner)
}

在这里插入图片描述
查询成功。

预加载全部

为方便观察与实验,在dog表上再关联一个dog_infos和dog_prices表
结构如下

type DogInfos struct {
	gorm.Model
	Age int
	DogsID int
}
type DogPrices struct {
	gorm.Model
	Price int
	DogsID int
}
type Dogs struct {
	gorm.Model
	Name     string
	OwnersID int
	DogInfos DogInfos
}

type Owners struct {
	gorm.Model
	Name string
	Dogs []Dogs
}

添加记录略古,直接展示dog_infos表记录
在这里插入图片描述
dog_prices
在这里插入图片描述

与创建、更新时使用 Select 类似,clause.Associations 也可以和 Preload 一起使用,它可以用来 预加载 全部关联,例如:

type User struct {
  gorm.Model
  Name       string
  CompanyID  uint
  Company    Company
  Role       Role
  Orders     []Order
}

db.Preload(clause.Associations).Find(&users)

实验案例如下

func QueryHasMany() {
	OpenDB()
	dog := &Dogs{}
	db.Preload(clause.Associations).First(dog)
	fmt.Println(dog)
}

在这里插入图片描述
clause.Associations 不会预加载嵌套的关联,但你可以使用嵌套预加载 例如:

db.Preload("Orders.OrderItems.Product").Preload(clause.Associations).Find(&users)

实验案例如下
直接查询Owner

func QueryHasMany() {
	OpenDB()
	owner := &Owners{}
	db.Preload(clause.Associations).First(owner)
	fmt.Println(owner)
}

在这里插入图片描述


为解决问题。
我单纯的认为,在一次预加载的嵌套下,就可以加载出所有dog关联表。自认为的查询代码

	db.Preload("Dogs").Preload(clause.Associations).First(owner)

但是结果并没有查询出info与price,问题待解决

带条件的预加载
GORM 允许带条件的 Preload 关联,类似于内联条件
// 带条件的预加载 Order
db.Preload("Orders", "state NOT IN (?)", "cancelled").Find(&users)
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2,3,4) AND state NOT IN ('cancelled');

db.Where("state = ?", "active").Preload("Orders", "state NOT IN (?)", "cancelled").Find(&users)
// SELECT * FROM users WHERE state = 'active';
// SELECT * FROM orders WHERE user_id IN (1,2) AND state NOT IN ('cancelled');

实验案例如下
从dogs表下手查询

func QueryHasMany() {
	OpenDB()
	dog := &[]Dogs{}
	db.Preload("DogInfos", "age < ?", "3").Find(dog)
	fmt.Println(dog)
}

在这里插入图片描述

结果发现,dog1年龄4岁,并没有显示dog1的info表,dog2年龄2岁,显示了dog2的info表,限制条件生效。
从owner表下手

func QueryHasMany() {
	OpenDB()
	//	dog := &[]Dogs{}
	owners := &Owners{}
	db.Preload("Dogs.DogInfos", "age < ?", "3").Find(owners, 1)
	fmt.Println(owners)
}

在这里插入图片描述
约束条件依然有效

自定义预加载 SQL

您可以通过 func(db *gorm.DB) *gorm.DB 实现自定义预加载 SQL,例如:

db.Preload("Orders", func(db *gorm.DB) *gorm.DB {
  return db.Order("orders.amount DESC")
}).Find(&users)
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2,3,4) order by orders.amount DESC;

实验案例如下
与上述的条件相同,只显示3岁以下的狗
直接从owner表下手

func QueryHasMany() {
   OpenDB()
   //	dog := &[]Dogs{}
   owners := &Owners{}
   db.Preload("Dogs.DogInfos", func(db *gorm.DB) *gorm.DB {
   	return db.Where("age<?", 3)
   }).Find(owners, 1)
   fmt.Println(owners)
}

在这里插入图片描述
结果时成立的,约束起了效果

预加载信息作为条件子句经行CRUD

在上述的基础上,我只想查询出owner1的两条狗中哪儿条狗年龄小于3岁,另外一条不出现,则需要info表里的信息作为约束条件,来约束对owner下的dog查询。
实验案例如下
在条件函数下使用Preload方法。

func QueryHasMany() {
	OpenDB()
	//	dog := &[]Dogs{}
	owners := &Owners{}
	db.Preload("Dogs", func(db *gorm.DB) *gorm.DB {
		return db.Preload("DogInfos", "age<3")
	}).Find(owners, 1)
	fmt.Println(owners)
}

在这里插入图片描述
还是显示了dog1的信息,只是没显示dog1的年龄,没有起到预期效果


推测预加载功能,还是会价值出目标表的内容,约束条件只在本层Preload中起效果


改用Joins方法:

func QueryHasMany() {
	OpenDB()
	//	dog := &[]Dogs{}
	owners := &Owners{}
	db.Preload("Dogs", func(db *gorm.DB) *gorm.DB {
		return db.Joins("DogInfos").Where("age<3")
	}).Find(owners, 1)
	fmt.Println(owners)
}

在这里插入图片描述
Joins函数起到了预期效果


Preload 在一个单独查询中加载关联数据。而 Join Preload 会使用 inner join 加载关联数据

;