钩子函数和高级查询操作 | 青训营

109 阅读5分钟

钩子函数

Hook 是在创建、查询、更新、删除等操作之前、之后调用的函数。

如果您已经为模型定义了指定的方法,它会在创建、更新、查询、删除时自动被调用。如果任何回调返回错误,GORM 将停止后续的操作并回滚事务。

钩子方法的函数签名应该是 func(*gorm.DB) error

创建 HOOK

在插入一条记录的时候,我希望做一点事情。

比如在插入数据前,我希望将 student 的默认赋值成 test@qq.com ,我们可以定义一个该结构体的方法,它就会在插入数据前生效。

func (user *Student) BeforeCreate(tx *gorm.DB) (err error) {  
	email := "text@qq.com"  
	user.Email = &email  
	return nil  
}

HOOK 接口

HOOK 有不少的接口,以满足在各种情况下的情况。所有的 HOOK 的用法和上面的例子基本类似。

  • 在创建时可用的 HOOK

// 开始事务
BeforeSave
BeforeCreate
// 关联前的 save
// 插入记录至 db
// 关联后的 save
AfterCreate
AfterSave
// 提交或回滚事务

一共有四个,分别是创建之前的可使用的 BeforeSaveBeforeCreate ;创建之后可使用的 AfterCreateAfterSave

  • 在更新时可用的 HOOK

// 开始事务
BeforeSave
BeforeUpdate
// 关联前的 save
// 更新 db
// 关联后的 save
AfterUpdate
AfterSave
// 提交或回滚事务

同样有有四个,分别是更新之前可使用的 BeforeSaveBeforeUpdate ;更新之后可使用的 AfterUpdateAfterSave

  • 在删除时可用的 HOOK

// 开始事务
BeforeDelete
// 删除 db 中的数据
AfterDelete
// 提交或回滚事务

只有两个,分别是删除之前可使用的 BeforeDelete ;删除之后可使用的 AfterDelete

  • 在查询时可用的 HOOK

// 从 db 中加载数据
// Preloading (eager loading)
AfterFind

只有一个,是查询之后可使用的 AfterFind

具体的实例在 GORM 文档的 HOOK 中有详细介绍。

高级查询操作

使用 Where 查询

使用 Where 查询相当于使用 SQL 中的 Where 来进行查询。我们将查询到的数据放入 studentList 的切片中。

查询用户名为李元芳的数据

DB.Where("name = ?", "李元芳").Find(&studentList) //第一种写法
DB.Find(&studentList, "name = ?", "李元芳") //第二种写法

上述的代码等效的 SQL 语句为:SELECT * FROM ` student1` WHERE name = '李元芳'

查询用户名不是李元芳的数据

DB.Where("name <> ?", "李元芳").Find(&studentList) //第一种写法

上述的代码等效的 SQL 语句为:SELECT * FROM `student1` WHERE name <> '李元芳'

DB.Where("not name = ?", "李元芳").Find(&studentList) //第二种写法

上述的代码等效的 SQL 语句为:SELECT * FROM `student1` WHERE not name = '李元芳'

DB.Not("name = ?", "李元芳").Find(&studentList) //第三种写法

上述的代码等效的 SQL 语句为:SELECT * FROM `student1` WHERE not name = '李元芳'

这些查询方式十分的灵活,有许多种写法...

查询用户名为李元芳、如燕的数据

DB.Where("name in (?)", []string{"李元芳", "如燕"}).Find(&studentList) //第一种写法
DB.Find(&studentList, "name in (?)", []string{"李元芳", "如燕"}) //第二种写法

上述的代码等效的 SQL 语句为:SELECT * FROM `student1` WHERE name in ('李元芳','如燕')

查询姓李的

模糊查询

DB.Where("name like ?", "李%").Find(&studentList)

上述的代码等效的 SQL 语句为:SELECT * FROM `student1` WHERE name like '李%'

如果是查询姓李的两个字的名字,则 李% 应该修改为 李_ ,如果是三个字的名字就是两个下划线 李__

查询年龄大于 23 且是使用 qq 邮箱的

DB.Where("age > ? and email like ?", "23", "%@qq.com").Find(&studentList)

上述的代码等效的 SQL 语句为:SELECT * FROM `student1` WHERE age > '23' and email like '%@qq. com'

如果是多个条件,使用 and 连接。

上述代码还可以写成两个 Where 连接使用,同样是表示与逻辑:

DB.Where("age > ?", "23").Where("email like ?", "%@qq.com").Find(&studentList)

查询是 qq 邮箱的,或者是女

DB.Where("email like ? or gender like ?", "%@qq.com", false).Find(&studentList)

上述的代码等效的 SQL 语句为:SELECT * FROM `student1` WHERE email like '%@qq. com' or gender like false

如果是多个条件,使用 or 连接。

同样的,上述代码还可以在后面加上 Or,同样是表示或·逻辑:

DB.Where("email like ?", "%@qq.com").Or("gender like ?", false).Find(&studentList)

使用结构体查询

使用结构体查询,在结构体中赋值上需要查询的条件即可。

结构体中赋值的成员就相当于是查询的条件,这些条件是与逻辑。

结构体会过滤零值,例如查找名字为李元芳并且年龄为 0 的数据:

DB.Where(&Student1{Name: "李元芳", Age: 0}).Find(&studentList)

此时的 SQL 语句并没有带上年龄: image.png

当把年龄修改成 32 的时候,就会带上年龄: image.png

使用 map 查询

使用 map 查询和结构体类似,map 查询不会过滤零值。

同样的,查询名字为李元芳并且年龄为 0 的数据:

DB.Where(map[string]any{"Name": "李元芳", "Age": 0}).Find(&studentList)

此时没有过滤零值: image.png

Not 条件

Not 函数是筛选掉你选择的条件。

例如我想筛选掉名字为李元芳的数据,也就是查询所有名字不为李元芳的数据:

DB.Not("name = ?", "李元芳").Find(&studentList)

我们在查询用户名不是李元芳的数据的时候就已经展示过了。

Or 条件

就是或逻辑。与 Where 中的 Or 其实是等价的:

DB.Or("email like ?", "%@qq.com").Or("gender like ?", false).Find(&studentList)

Select 选择字段

当我们只需要查询某个字段的所有的数据的时候,可以选择使用 Select 函数。

因为 Find 函数是查询所有字段的所有数据,加上 Select 会更加高效。

例如我只需要表中的所有名字,而不需要其他的数据。

  • 当使用 Find 函数
DB.Find(&studentList)

image.png

它会查询所有数据,这样比较耗时

  • 加上 Select 函数之后
DB.Select("name").Find(&studentList)

image.png 这样就只会返回名字数据,其它为零值,更加高效和具有目的性

如果需要选择多个字段,使用逗号隔开继续添加字段名或者使用字符串列表

DB.Select("name", "age").Find(&studentList) //第一种写法
DB.Select([]string{"name", "age"}).Find(&studentList) //第二种写法

因为有时我们只需要其中的一个字段或几个字段的数据,使用整个模型去存储比较浪费空间,我们可以使用 Scan 函数,将获取到的字段数据存入另一个比较小的结构体。

type User struct {  
	Name string  
	Age int  
}  
var userList []User  
  
DB.Model(Student1{}).Select("name", "age").Scan(&userList)

我们定义了一个只存储名字和年龄的结构体,然后需要只用 Model 函数指定要操作的数据库模型或表。使用 Select 选择要查询的字段,最后使用 Scan 将其扫描进 userList 中。

完整代码地址:hdheid/GormStudy