GORM-学习

5,852 阅读6分钟

GORM想要传达的一些概念

学习GORM之前需要理解ORM中的一些概念, 理解这些概念对于流畅使用GORM函数会有非常大的帮助, 尝试带着SQL的理念在这里使用, 最后很容易混淆, 对于GORM的使用也止步于简单操作, RAW SQL

  1. 怎么用ORM模型设置关联关系 跳转
  2. 外键是怎么表示的, 如何设置默认外键 跳转
  3. 如何取消主键限制, 如何使用自定义外键 跳转
  4. 什么是Association, 如何管理你的关联关系 跳转
  5. 利用Many2Many创建关联表 跳转
  6. 利用Preload做联表 跳转

观念上的变化

场景: 假设存在卡片card-1/2/3, 卡片之间存在某种关联关系

  • 在Go程序中分别创建结构体 Cards 以及 CardAssociation
  • 在SQL中创建表: cards + card_associations
  • 当我们需要创建卡片的时候执行create cards.., 当我们需要创建关联的时候我们执行create associations..

→ 因此在传统SQL观念里, 我们单独设置出两张表, 然后有需要来了, 我们会单独对这两张表操作. 下面看看在GORM里是怎么做的

type Card struct {
  gorm.Model
  Attribute1 string
  Attribute2 string 
  AssociatedCards []Card 
}

→ 与传统SQL最大的不同出现了,在Card结构体里, 我们嵌套了一些其他Card结构体, 传统SQL中"相互关联"的概念, 在ORM中成为了"相互拥有"的概念.

  • 传统SQL: card-1 ~ card-2 关联关系
  • GORM: card-1 > card-2 拥有关系

外键设定

一旦能接受新的模式, 就可以说一说外键设定了. 两个结构体之间相互关联, 最直接的想法是, 我怎么从一个结构体 出发然后去获得另一个结构体,

我怎么去声明一条外键

如果我需要通过User去查找它拥有哪些CreditCard, 那么实际上我做的事情 = "拿着User主键去CreditCard表查询". 这个User的主键, 在CreditCard表里叫什么名字? 这个名字就是我们即将设置的外键. 请牢记这个概念

不设置外键, 使用默认外键

type CreditCard struct {
	gorm.Model
	Number   string
	UserID  uint
}
type User struct {
	gorm.Model
	CreditCards []CreditCard
}

在GORM里我们只要创建这两个表, 关联关系就生成了, User就会包含很多个CreditCard对象.假设现在你希望通过User去查询它 关联了那些卡片, 你可以这么做:

target := []CreditCard{}
source := &User{
	Model:gorm.Model{
		ID:1,
	},
}
database.Model(source).Related(&target)

继续外键设置的问题, 如果我们不去指定外键名称, 那么我们拿着User主键去CreditCard查询的时候, 默认使用的属性名:user_id, 执行:

select * from credit_cards where user_id = 123;

请注意一个坑, 默认外键是: 表名+id,

  • User表, 默认外键名user_id
  • abc表, 默认外键名称abc_id

手动指定外键

type User struct {
  gorm.Model
  MemberNumber string
  CreditCards  []CreditCard `gorm:"foreignkey:UserMemberNumber;association_foreignkey:MemberNumber"`
}

type CreditCard struct {
  gorm.Model
  Number           string
  UserMemberNumber string
}
  • foreignkey 在此之前,CreditCard表里必须要有User_ID, 作为外键, 但是指定foreign_key之后, 就不是一定要有User_id字段了
  • association_foreignkey, 在之前我们都是规定, 必须使用 UserID(主键) 去CreditCard表里查找, 但是使用了这个Tag以后你就可以通过MemberNumber (非主键) 去查找了

联表 - Association

一旦理解外键是怎么设置的, 我们就可以开始用上外键了, 概念:Association 是一个笼统的工具, 用于管理所有关联关系,以上面的Card&User为例, 我们来管理以上 两表之间的关联关系(上面两表,采用默认外键/主键的方式相互关联)

cs := []CreditCard{}
xiaohan := &User{
	Model:gorm.Model{
		ID:1,
	},
}

// 所有与xiaohan(id=1)相关联的CreditCard,找出来
d.Model(xiaohan).Association("CreditCards").Find(&cs)

// xiaohan数据取消与ID=1的CreditCard取消关联
d.Model(xiaohan).Association("CreditCards").Delete(&CreditCard{
	Model:gorm.Model{
		ID:1,
	},
})

// xiaohan与CreditCard之间添加关联 
d.Model(xiaohan).Association("CreditCards").Append(&cards)
// 取消所有关联
db.Model(xiaohan).Association("Languages").Clear()
// 对象关联计数
db.Model(xiaohan).Association("Languages").Count()

关联创建的时候存在一个坑,这个坑不注意可能会导致你的数据被抹掉 意识到,User→CreditCard时, 一个User面对的是多个CreditCard →因此在Append里面填充的是数组,而不是一个单个的对象.

  • 如果一对一的关联关系, 填充单个对象
  • 如果是一对多的关系, 一定要填充对象组(&[]Array). 否则会报错找不到, 默认策略中找不到会创建新的, 来替代, 从而数据被洗掉
    • 取消找不到创建新的替代, 策略
    • 先尝试Find再去Append关联关系

学会利用many2many管理关联关系

card_id association_id
card-1 card-2
card-1 card-3

在你创建表以后, AssociationCards 虽然也是一条属性, 但是并不会像别的字段一样出现在表里, 因为在这里我们讨论的是关联 这个字段被忽略了, 不会出现在表中.取而代之的是出现了一张表: 名字就叫做card_associations

正如你想表示的那样, 一个Card对象关联着许多别的Card对象, 在这张关联表card_associations中, 一方面是你的card_id 另一方面也是你设置的association_id.

简单说, 就是你不用手动去设置了,经过这样描述以后, 表会为你创建

学会利用这种关系

// 创建一张卡片, 不做任何关联关系, ID自增
database.Create(&Card{})

// 创建一张卡片, 同时关联一张卡片
// 这里如果卡片存在, 直接关联, 如果不存在则会为你关联
database.Create(&Card{
	AssociatedCards: []Card{
		{
			Model:gorm.Model{ID:2},
		},
	},
})

// 只关联两张卡片,将4&7两张卡片关联起来
database.Model(&Card{Model:gorm.Model{ID:7}}).
	       Association("AssociatedCards").
	       Append(&Card{Model:gorm.Model{ID:4}})

联表 - Preload

事实上,理解了Association这种思想以后,再去理解Preload就会容易一些, 在GORM里关联/外键这样的概念被转换成结构体之间的相互包含. 继续上面的例子,聊聊Card之间的"自关联", 怎么去查询id=3卡片所关联的卡片?

type Card struct {
  ID              -> 3
  AssociatedCards -> ?
}

// 我们这样做:
item := &Card{
	Model:gorm.Model{
		ID:3,
	},
}
database.Preload("AssociatedCards").Find(item)
fmt.Printf("Here the item is %v \n",item)

一级操作:Find 这行代码的执行过程是这样的,上来先执行Find(&item), 也就是我们要先查询出ID=3的对象出来, 既然查出来了, 接下来就可以查看它关联了那些卡片了.

二级操作:最外Preload 我们做Preload, AssociatedCards是一个属性, 同时在这里也象征了,所有关联的卡片, 我们只要取出这一行, 就能拿出所有关联的属性

三级操作:次外Preload ...

如果理解以上Preload的执行原理, 以及执行顺序以后,我们就可以开始在上面玩一些花样了,我们查出来的是一些Card对象, 玩的原理是对各级操作做限制, 诸如 order , where , not in 之类的操作

// 对一级操作Find上限制,这里的where是作用于Find的
database.Where(xxx)
         Preload("AssociatedCards").
         Find(item)

// 对二级操作上限制 - 排序
database.Preload("AssociatedCards",  func(db *gorm.DB) *gorm.DB {
	return db.Order("cards.id DESC")
}).Find(item)

// 对二级操作上限制 - 操作
database.Preload("AssociatedCards", "id not in (?)","1,2").Find(item)