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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
日志服务 SLS,月写入数据量 50GB 1个月
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 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)
}
相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
3月前
|
运维 监控 算法
监控局域网其他电脑:Go 语言迪杰斯特拉算法的高效应用
在信息化时代,监控局域网成为网络管理与安全防护的关键需求。本文探讨了迪杰斯特拉(Dijkstra)算法在监控局域网中的应用,通过计算最短路径优化数据传输和故障检测。文中提供了使用Go语言实现的代码例程,展示了如何高效地进行网络监控,确保局域网的稳定运行和数据安全。迪杰斯特拉算法能减少传输延迟和带宽消耗,及时发现并处理网络故障,适用于复杂网络环境下的管理和维护。
|
3月前
|
编译器 Go
揭秘 Go 语言中空结构体的强大用法
Go 语言中的空结构体 `struct{}` 不包含任何字段,不占用内存空间。它在实际编程中有多种典型用法:1) 结合 map 实现集合(set)类型;2) 与 channel 搭配用于信号通知;3) 申请超大容量的 Slice 和 Array 以节省内存;4) 作为接口实现时明确表示不关注值。此外,需要注意的是,空结构体作为字段时可能会因内存对齐原因占用额外空间。建议将空结构体放在外层结构体的第一个字段以优化内存使用。
|
20天前
|
Go API 定位技术
MCP 实战:用 Go 语言开发一个查询 IP 信息的 MCP 服务器
随着 MCP 的快速普及和广泛应用,MCP 服务器也层出不穷。大多数开发者使用的 MCP 服务器开发库是官方提供的 typescript-sdk,而作为 Go 开发者,我们也可以借助优秀的第三方库去开发 MCP 服务器,例如 ThinkInAIXYZ/go-mcp。 本文将详细介绍如何在 Go 语言中使用 go-mcp 库来开发一个查询 IP 信息的 MCP 服务器。
82 0
|
3月前
|
存储 缓存 监控
企业监控软件中 Go 语言哈希表算法的应用研究与分析
在数字化时代,企业监控软件对企业的稳定运营至关重要。哈希表(散列表)作为高效的数据结构,广泛应用于企业监控中,如设备状态管理、数据分类和缓存机制。Go 语言中的 map 实现了哈希表,能快速处理海量监控数据,确保实时准确反映设备状态,提升系统性能,助力企业实现智能化管理。
53 3
|
3月前
|
存储 缓存 安全
Go 语言中的 Sync.Map 详解:并发安全的 Map 实现
`sync.Map` 是 Go 语言中用于并发安全操作的 Map 实现,适用于读多写少的场景。它通过两个底层 Map(`read` 和 `dirty`)实现读写分离,提供高效的读性能。主要方法包括 `Store`、`Load`、`Delete` 等。在大量写入时性能可能下降,需谨慎选择使用场景。
|
3月前
|
SQL 安全 Java
阿里双十一背后的Go语言实践:百万QPS网关的设计与实现
解析阿里核心网关如何利用Go协程池、RingBuffer、零拷贝技术支撑亿级流量。 重点分享: ① 如何用gRPC拦截器实现熔断限流; ② Sync.Map在高并发读写中的取舍。
122 0
|
3月前
|
存储 Go
Go 语言入门指南:切片
Golang中的切片(Slice)是基于数组的动态序列,支持变长操作。它由指针、长度和容量三部分组成,底层引用一个连续的数组片段。切片提供灵活的增减元素功能,语法形式为`[]T`,其中T为元素类型。相比固定长度的数组,切片更常用,允许动态调整大小,并且多个切片可以共享同一底层数组。通过内置的`make`函数可创建指定长度和容量的切片。需要注意的是,切片不能直接比较,只能与`nil`比较,且空切片的长度为0。
Go 语言入门指南:切片
|
3月前
|
算法 安全 Go
公司局域网管理系统里的 Go 语言 Bloom Filter 算法,太值得深挖了
本文探讨了如何利用 Go 语言中的 Bloom Filter 算法提升公司局域网管理系统的性能。Bloom Filter 是一种高效的空间节省型数据结构,适用于快速判断元素是否存在于集合中。文中通过具体代码示例展示了如何在 Go 中实现 Bloom Filter,并应用于局域网的 IP 访问控制,显著提高系统响应速度和安全性。随着网络规模扩大和技术进步,持续优化算法和结合其他安全技术将是企业维持网络竞争力的关键。
76 2
公司局域网管理系统里的 Go 语言 Bloom Filter 算法,太值得深挖了
|
3月前
|
监控 Linux PHP
【02】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-2月12日优雅草简化Centos stream8安装zabbix7教程-本搭建教程非docker搭建教程-优雅草solution
【02】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-2月12日优雅草简化Centos stream8安装zabbix7教程-本搭建教程非docker搭建教程-优雅草solution
113 20