Go语言之GORM框架(三)——Hook(钩子)与Gorm的高级查询

本文涉及的产品
云数据库 RDS MySQL,集群版 2核4GB 100GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: Go语言之GORM框架(三)——Hook(钩子)与Gorm的高级查询

Hook(钩子)

和我们在gin框架中讲解的Hook函数一样,我们也可以在定义Hook结构体,完成一些操作,相关接口声明如下:

type CreateUser interface {    //创建对象时使用的Hook
  BeforeCreate() error
  BeforeSave() error
  AfterCreate() error
  AfterSave() error
}
type UpdateUser interface {
  BeforeUpdate() error
  BeforeSave() error
  AfterUpdate() error
  AfterSave() error
}
type DeleteUser interface {
  BeforeDelete() error
  AfterDelete() error
}
type FindUser interface {
  AfterFind() error
}

我们可以根据自己的需求来订制我们所需要的Hook函数,示例:

func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
  u.UUID = uuid.New()
  if !u.IsValid() {
    err = errors.New("can't save invalid data")
  }
  return
}
func (u *User) AfterCreate(tx *gorm.DB) (err error) {
  if u.ID == 1 {
    tx.Model(u).Update("role", "admin")
  }
  return
}

注意

  • Hook函数在执行过程的执行时间有规定的时间,以创建对象的Hook为例:
// 开始事务
BeforeSave
BeforeCreate
// 关联前的 save
// 插入记录至 db
// 关联后的 save
AfterCreate
AfterSave
// 提交或回滚事务

具体可以参考官方文档:

Hook

  • 在 GORM 中保存、删除操作会默认运行在事务上, 因此在事务完成之前该事务中所作的更改是不可见的,如果Hook返回了任何错误,则修改将被回滚。

高级查询

初始化相关表

package main
import (
  "fmt"
  "gorm.io/driver/mysql"
  "gorm.io/gorm"
  "gorm.io/gorm/logger"
  "log"
  "os"
  "time"
)
type Employee struct {
  ID    uint    `gorm:"size:3"`
  Name  string  `gorm:"size:8"`
  Age   int     `gorm:"size:3"`
  Sex   bool    `gorm:"size:3"`
  Email *string `gorm:"size:32"`
}
var myDB *gorm.DB
func init() {
  //连接数据库
  user := "root"
  password := "ba161754"
  dbname := "gorm"
  ip := "127.0.0.1"
  port := "3306"
  dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", user, password, ip, port, dbname)
  db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
  if err != nil {
    fmt.Println("数据库连接失败,err:", err)
    return
  }
  fmt.Println("数据库连接成功")
  myDB = db
  //初始化日志
  var mysqlLogger logger.Interface
  mysqlLogger = logger.Default.LogMode(logger.Info) //设置日志打印级别
  mysqlLogger = logger.New(
    log.New(os.Stdout, "\r\n", log.LstdFlags), // (日志输出的目标,前缀和日志包含的内容)
    logger.Config{
      SlowThreshold:             time.Second, // 慢 SQL 阈值
      LogLevel:                  logger.Info, // 日志级别
      IgnoreRecordNotFoundError: true,        // 忽略ErrRecordNotFound(记录未找到)错误
      Colorful:                  true,        // 使用彩色打印
    },
  )
  myDB.Logger = mysqlLogger
  //创建所要使用的单表
  err = myDB.AutoMigrate(&Employee{})
  if err != nil {
    fmt.Println("创建表失败,err:", err)
    return
  }
  //插入测试数据
  employeeList := []Employee{
    {ID: 1, Name: "李元芳", Age: 32, Email: PtrString("lyf@yf.com"), Sex: true},
    {ID: 2, Name: "张武", Age: 18, Email: PtrString("zhangwu@lly.cn"), Sex: true},
    {ID: 3, Name: "枫枫", Age: 23, Email: PtrString("ff@yahoo.com"), Sex: true},
    {ID: 4, Name: "刘大", Age: 54, Email: PtrString("liuda@qq.com"), Sex: true},
    {ID: 5, Name: "李武", Age: 23, Email: PtrString("liwu@lly.cn"), Sex: true},
    {ID: 6, Name: "李琦", Age: 14, Email: PtrString("liqi@lly.cn"), Sex: false},
    {ID: 7, Name: "晓梅", Age: 25, Email: PtrString("xiaomeo@sl.com"), Sex: false},
    {ID: 8, Name: "如燕", Age: 26, Email: PtrString("ruyan@yf.com"), Sex: false},
    {ID: 9, Name: "魔灵", Age: 21, Email: PtrString("moling@sl.com"), Sex: true},
  }
  myDB.Create(&employeeList)
}
func PtrString(email string) *string {
  return &email
}
func main() {
}

Where查询

  • 简单示例:
var employee Employee
  //Where
  myDB.Where("name like ?", "李%").Find(&employee) //查询姓李的
  fmt.Println(employee)
  • Not条件
myDB.Not("name like ?", "李%").Find(&employee) //查询第一条不是姓李的
  fmt.Println(employee)
  • Or条件
var employeeList []Employee
  myDB.Not("name like ?", "李%").Or("age>20").Find(&employeeList)  //用Where表示and
  for _, value := range employeeList {
    data, _ := json.Marshal(value)
    fmt.Println(string(data))
  }
  • And条件
employeeList=[]Employee{}
  myDB.Not("name like ?", "李%").Where("age>20").Find(&employeeList)  //用Where表示and
  for _, value := range employeeList {
    data, _ := json.Marshal(value)
    fmt.Println(string(data))
  }

select选择字段

  • 简单示例
employeeList := []Employee{}
  myDB.Select("name", "age").Find(&employeeList)
  for _, value := range employeeList {
    data, _ := json.Marshal(value)
    fmt.Println(string(data))
  }
  • Scan函数
    我们可以用Scan函数将搜索结果导入带新的结构体中
type Employee1 struct {
  Name string
  Age  int
}
  //select
  employeeList := []Employee{}
  employeeList1 := []Employee1{}
  myDB.Select("name", "age").Find(&employeeList).Scan(&employeeList1)
  for _, value := range employeeList1 {
    data, _ := json.Marshal(value)
    fmt.Println(string(data))
  }

输出为:

排序

//排序
  employeeList := []Employee{}
  myDB.Order("age desc").Find(&employeeList)
  for _, value := range employeeList {
    data, _ := json.Marshal(value)
    fmt.Println(string(data))
  }

分页查询

//分页
  employeeList := []Employee{}
  myDB.Limit(4).Offset(0).Order("age desc").Find(&employeeList) //Limit:每页限定记录数,offset:偏移量
  for _, value := range employeeList {
    data, _ := json.Marshal(value)
    fmt.Println(string(data))
  }

去重

//去重
  var agelist []int
  myDB.Table("employees").Select("distinct age").Find(&agelist)
  for _, value := range agelist {
    fmt.Println(value)
  }

分组查询

//分组查询
  var ageList []int
  // 查询男生的个数和女生的个数
  myDB.Table("employees").Select("count(id)").Group("Sex").Scan(&ageList)
  fmt.Println(ageList)

执行原生sql

//执行原生sql
  type SexGroup struct {
    Count int `gorm:"column:count(id)"`
    Sex   bool
    Name  string `gorm:"column:group_concat(name)"`
  }
  var sexlist []SexGroup
  myDB.Raw("select count(id) ,sex,group_concat(name) from employees group by sex").Scan(&sexlist)
  for _, value := range sexlist {
    data, _ := json.Marshal(value)
    fmt.Println(string(data))
  }
}

子查询

//子查询
  //select * from students where age > (select avg(age) from students); 原生sql
  myDB.Where("age > (?)", myDB.Model(&Employee{}).Select("avg(age)")).Find(&employee)
  fmt.Println(employee)

查询调用

我们可以在model层写一些通用的查询方法,让外界直接来调用:

func Age23(db *gorm.DB) *gorm.DB {
  return db.Where("age>?", 23)
}
  myDB.Scopes(Age23).Find(&employee)
  fmt.Println(employee)

完整代码

package main
import (
  "encoding/json"
  "fmt"
  "gorm.io/driver/mysql"
  "gorm.io/gorm"
  "gorm.io/gorm/logger"
  "log"
  "os"
  "time"
)
type Employee struct {
  ID    uint    `gorm:"size:3"`
  Name  string  `gorm:"size:8"`
  Age   int     `gorm:"size:3"`
  Sex   bool    `gorm:"size:3"`
  Email *string `gorm:"size:32"`
}
type Employee1 struct {
  Name string
  Age  int
}
var myDB *gorm.DB
func init() {
  //连接数据库
  user := "root"
  password := "ba161754"
  dbname := "gorm"
  ip := "127.0.0.1"
  port := "3306"
  dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", user, password, ip, port, dbname)
  db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
  if err != nil {
    fmt.Println("数据库连接失败,err:", err)
    return
  }
  fmt.Println("数据库连接成功")
  myDB = db
  //初始化日志
  var mysqlLogger logger.Interface
  mysqlLogger = logger.Default.LogMode(logger.Info) //设置日志打印级别
  mysqlLogger = logger.New(
    log.New(os.Stdout, "\r\n", log.LstdFlags), // (日志输出的目标,前缀和日志包含的内容)
    logger.Config{
      SlowThreshold:             time.Second, // 慢 SQL 阈值
      LogLevel:                  logger.Info, // 日志级别
      IgnoreRecordNotFoundError: true,        // 忽略ErrRecordNotFound(记录未找到)错误
      Colorful:                  true,        // 使用彩色打印
    },
  )
  myDB.Logger = mysqlLogger
  //创建所要使用的单表
  err = myDB.AutoMigrate(&Employee{})
  if err != nil {
    fmt.Println("创建表失败,err:", err)
    return
  }
  //插入测试数据
  employeeList := []Employee{
    {ID: 1, Name: "李元芳", Age: 32, Email: PtrString("lyf@yf.com"), Sex: true},
    {ID: 2, Name: "张武", Age: 18, Email: PtrString("zhangwu@lly.cn"), Sex: true},
    {ID: 3, Name: "枫枫", Age: 23, Email: PtrString("ff@yahoo.com"), Sex: true},
    {ID: 4, Name: "刘大", Age: 54, Email: PtrString("liuda@qq.com"), Sex: true},
    {ID: 5, Name: "李武", Age: 23, Email: PtrString("liwu@lly.cn"), Sex: true},
    {ID: 6, Name: "李琦", Age: 14, Email: PtrString("liqi@lly.cn"), Sex: false},
    {ID: 7, Name: "晓梅", Age: 25, Email: PtrString("xiaomeo@sl.com"), Sex: false},
    {ID: 8, Name: "如燕", Age: 26, Email: PtrString("ruyan@yf.com"), Sex: false},
    {ID: 9, Name: "魔灵", Age: 21, Email: PtrString("moling@sl.com"), Sex: true},
  }
  myDB.Create(&employeeList)
}
func PtrString(email string) *string {
  return &email
}
func Age23(db *gorm.DB) *gorm.DB {
  return db.Where("age>?", 23)
}
func main() {
  employee := Employee{}
  employeeList:=[]Employee{}
  //Where
  myDB.Where("name like ?", "李%").Find(&employee) //查询姓李的
  fmt.Println(employee)
  
  myDB.Not("name like ?", "李%").Find(&employee) //查询第一条不是姓李的
  fmt.Println(employee)
  
  myDB.Not("name like ?", "李%").Or("age>20").Find(&employeeList) //用Where表示and
  for _, value := range employeeList {
    data, _ := json.Marshal(value)
    fmt.Println(string(data))
  }
  
  employeeList = []Employee{}
  myDB.Not("name like ?", "李%").Where("age>20").Find(&employeeList) //用Where表示and
  for _, value := range employeeList {
    data, _ := json.Marshal(value)
    fmt.Println(string(data))
  }
  //select
  employeeList = []Employee{}
  employeeList1 := []Employee1{}
  myDB.Select("name", "age").Find(&employeeList).Scan(&employeeList1)
  for _, value := range employeeList1 {
    data, _ := json.Marshal(value)
    fmt.Println(string(data))
  }
  //排序
  employeeList = []Employee{}
  myDB.Order("age desc").Find(&employeeList)
  for _, value := range employeeList {
    data, _ := json.Marshal(value)
    fmt.Println(string(data))
  }
  //分页
  employeeList = []Employee{}
  myDB.Limit(4).Offset(0).Order("age desc").Find(&employeeList) //Limit:每页限定记录数,offset:偏移量
  for _, value := range employeeList {
    data, _ := json.Marshal(value)
    fmt.Println(string(data))
  }
  //去重
  var agelist []int
  myDB.Table("employees").Select("distinct age").Find(&agelist)
  for _, value := range agelist {
    fmt.Println(value)
  }
  //分组查询
  var ageList []int
  // 查询男生的个数和女生的个数
  myDB.Table("employees").Select("count(id)").Group("Sex").Scan(&ageList)
  fmt.Println(ageList)
  //执行原生sql
  type SexGroup struct {
    Count int `gorm:"column:count(id)"`
    Sex   bool
    Name  string `gorm:"column:group_concat(name)"`
  }
  var sexlist []SexGroup
  myDB.Raw("select count(id) ,sex,group_concat(name) from employees group by sex").Scan(&sexlist)
  for _, value := range sexlist {
    data, _ := json.Marshal(value)
    fmt.Println(string(data))
  }
  //子查询
  //select * from students where age > (select avg(age) from students); 原生sql
  myDB.Where("age > (?)", myDB.Model(&Employee{}).Select("avg(age)")).Find(&employee)
  fmt.Println(employee)
  //查询引用Scope
  myDB.Scopes(Age23).Find(&employee)
  fmt.Println(employee)
}
相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
5天前
|
JSON 中间件 Go
Go语言Web框架Gin介绍
【7月更文挑战第19天】Gin是一个功能强大、高性能且易于使用的Go语言Web框架。它提供了路由、中间件、参数绑定等丰富的功能,帮助开发者快速构建高质量的Web应用。通过本文的介绍,你应该对Gin框架有了初步的了解,并能够使用它来开发简单的Web服务。随着你对Gin的深入学习和实践,你将能够利用它构建更复杂、更强大的Web应用。
|
10天前
|
Cloud Native Java Go
为什么要学习Go语言?
GO logo的核心理念,即简单胜于复杂。使用现代斜体无衬线字体与三条简单的运动线相结合,形成一个类似于快速运动的两个轮子的标记,传达速度和效率。字母的圆形暗示了GO地鼠的眼睛,创造了一个熟悉的形状,让标记和吉祥物很好地搭配在一起。
26 4
|
6天前
|
Oracle 关系型数据库 MySQL
|
12天前
|
安全 Go
Go语言map并发安全,互斥锁和读写锁谁更优?
Go并发编程中,`sync.Mutex`提供独占访问,适合读写操作均衡或写操作频繁的场景;`sync.RWMutex`允许多个读取者并行,适用于读多写少的情况。明智选择锁可提升程序性能和稳定性。示例展示了如何在操作map时使用这两种锁。
21 0
|
12天前
|
安全 Go 开发者
Go语言map并发安全使用的正确姿势
在Go并发编程中,由于普通map不是线程安全的,多goroutine访问可能导致数据竞态。为保证安全,可使用`sync.Mutex`封装map或使用从Go 1.9开始提供的`sync.Map`。前者通过加锁手动同步,后者内置并发控制,适用于多goroutine共享。选择哪种取决于具体场景和性能需求。
13 0
|
14天前
|
JSON 测试技术 Go
零值在go语言和初始化数据
【7月更文挑战第10天】本文介绍在Go语言中如何初始化数据,未初始化的变量会有对应的零值:bool为`false`,int为`0`,byte和string为空,pointer、function、interface及channel为`nil`,slice和map也为`nil`。。本文档作为指南,帮助理解Go的数据结构和正确使用它们。
62 22
零值在go语言和初始化数据
|
16天前
|
安全 算法 程序员
在go语言中使用泛型和反射
【7月更文挑战第8天】本文介绍go支持泛型后,提升了代码复用,如操作切片、映射、通道的函数,以及自定义数据结构。 泛型适用于通用数据结构和函数,减少接口使用和类型断言。
86 1
在go语言中使用泛型和反射
|
18天前
|
缓存 编译器 Shell
回顾go语言基础中一些特别的概念
【7月更文挑战第6天】本文介绍Go语言基础涵盖包声明、导入、函数、变量、语句和表达式以及注释。零值可用类型如切片、互斥锁和缓冲,支持预分配容量以优化性能。
47 2
回顾go语言基础中一些特别的概念
|
15天前
|
JSON Java Go
Go 语言性能优化技巧
在Go语言中优化性能涉及数字字符串转换(如用`strconv.Itoa()`代替`fmt.Sprintf()`)、避免不必要的字符串到字节切片转换、预分配切片容量、使用`strings.Builder`拼接、有效利用并发(`goroutine`和`sync.WaitGroup`)、减少内存分配、对象重用(`sync.Pool`)、无锁编程、I/O缓冲、正则预编译和选择高效的序列化方法。这些策略能显著提升代码执行效率和系统资源利用率。
49 13
|
15天前
|
设计模式 Go
Go语言设计模式:使用Option模式简化类的初始化
在Go语言中,面对构造函数参数过多导致的复杂性问题,可以采用Option模式。Option模式通过函数选项提供灵活的配置,增强了构造函数的可读性和可扩展性。以`Foo`为例,通过定义如`WithName`、`WithAge`、`WithDB`等设置器函数,调用者可以选择性地传递所需参数,避免了记忆参数顺序和类型。这种模式提升了代码的维护性和灵活性,特别是在处理多配置场景时。
46 8