跳转至

7.6 GORM框架深度应用

学习目标

  • 掌握GORM框架的核心概念和使用方法
  • 熟练定义模型和处理关联关系
  • 学会GORM的高级查询技巧
  • 理解GORM的性能优化策略

核心内容

1. GORM框架概述

GORM是Go语言中最流行的ORM(对象关系映射)库,它简化了Go应用与数据库的交互过程,提供了强大的CRUD操作、关联关系处理和查询构建能力。

1.1 GORM的设计理念

GORM的设计遵循"约定优于配置"的原则,通过合理的默认设置减少开发者的配置工作。它采用链式调用的API设计,使代码更具可读性和可维护性。同时,GORM完全支持Go的类型系统,并提供了灵活的扩展机制。

1.2 安装与基础配置

安装GORM非常简单,使用go get命令即可:

go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite  # 根据需要选择数据库驱动
# 其他数据库驱动:
# go get -u gorm.io/driver/mysql
# go get -u gorm.io/driver/postgres
# go get -u gorm.io/driver/sqlserver

基础配置示例:

package main

import (
  "gorm.io/driver/sqlite"
  "gorm.io/gorm"
)

func main() {
  // SQLite配置
  db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
  if err != nil {
    panic("failed to connect database")
  }

  // MySQL配置示例
  // dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
  // db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})

  // PostgreSQL配置示例
  // dsn := "host=localhost user=gorm password=gorm dbname=gorm port=5432 sslmode=disable TimeZone=Asia/Shanghai"
  // db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})

  // 使用db进行数据库操作...
}

1.3 数据库连接管理

GORM提供了连接池配置功能,可以通过获取底层sql.DB来设置:

package main

import (
  "database/sql"
  "gorm.io/driver/sqlite"
  "gorm.io/gorm"
  "time"
)

func main() {
  db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
  if err != nil {
    panic("failed to connect database")
  }

  // 获取底层sql.DB
  sqlDB, err := db.DB()
  if err != nil {
    panic("failed to get underlying DB")
  }

  // 设置连接池参数
  sqlDB.SetMaxIdleConns(10)           // 最大空闲连接数
  sqlDB.SetMaxOpenConns(100)          // 最大打开连接数
  sqlDB.SetConnMaxLifetime(time.Hour) // 连接的最大生存期
}

2. 模型定义与约定

模型是GORM中的核心概念,它是Go结构体与数据库表之间的映射。

2.1 结构体标签详解

GORM使用结构体标签来定义模型与数据库表之间的映射关系:

package main

import (
  "time"
  "gorm.io/gorm"
)

// User 模型定义
type User struct {
  ID        uint           `gorm:"primaryKey"` // 主键
  Name      string         `gorm:"size:100;not null"` // 字符串长度限制,非空
  Email     string         `gorm:"uniqueIndex;not null"` // 唯一索引,非空
  Age       int            `gorm:"default:0;check:age > 0"` // 默认值,检查约束
  Birthday  *time.Time     `gorm:"index"` // 索引
  CreatedAt time.Time      // 创建时间
  UpdatedAt time.Time      // 更新时间
  DeletedAt gorm.DeletedAt `gorm:"index"` // 软删除标记
}

常用的GORM标签: - primaryKey: 指定为主键 - autoIncrement: 自动增长 - size:n: 指定字段大小 - default:value: 设置默认值 - not null: 非空约束 - unique: 唯一约束 - index: 创建索引 - uniqueIndex: 创建唯一索引 - column:name: 指定数据库列名 - type:varchar(100): 指定数据库类型

2.2 字段类型映射

GORM会自动将Go类型映射到数据库类型,常见的类型映射关系:

Go类型 数据库类型
uint, uint8, uint16, uint32, uint64 INTEGER
int, int8, int16, int32, int64 INTEGER
float32, float64 REAL, DOUBLE PRECISION
bool BOOLEAN
string VARCHAR(255)
time.Time DATETIME, TIMESTAMP
[]byte BLOB, BYTEA

可以通过type标签自定义数据库类型:

type Product struct {
  ID    uint
  Name  string
  Price float64 `gorm:"type:decimal(10,2)"` // 自定义为decimal类型
  Data  []byte  `gorm:"type:longblob"`      // 自定义为longblob类型
}

2.3 模型约定与自定义

GORM有一些默认约定: - 结构体名的蛇形复数形式作为表名 - ID字段作为主键 - CreatedAt、UpdatedAt字段自动管理创建和更新时间 - DeletedAt字段用于软删除

可以通过实现接口自定义这些约定:

package main

import (
  "gorm.io/gorm"
  "time"
)

// 自定义表名
type User struct {
  ID   uint
  Name string
}

// TableName 自定义表名
func (User) TableName() string {
  return "sys_user"
}

// 自定义时间字段
type Article struct {
  ID        uint
  Title     string
  CreateTime time.Time `gorm:"autoCreateTime"` // 自动设置创建时间
  UpdateTime time.Time `gorm:"autoUpdateTime"` // 自动设置更新时间
}

// 全局禁用表名复数
func main() {
  db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{
    NamingStrategy: schema.NamingStrategy{
      SingularTable: true, // 使用单数表名
    },
  })
}

3. 基础CRUD操作

CRUD(创建、读取、更新、删除)是数据库操作的基础,GORM提供了简洁的API来实现这些操作。

3.1 创建记录的多种方式

package main

import (
  "gorm.io/driver/sqlite"
  "gorm.io/gorm"
)

type User struct {
  gorm.Model
  Name  string
  Email string
  Age   int
}

func main() {
  db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
  if err != nil {
    panic("failed to connect database")
  }

  // 迁移 schema
  db.AutoMigrate(&User{})

  // 1. 创建单条记录
  user := User{Name: "John Doe", Email: "john@example.com", Age: 30}
  result := db.Create(&user) // 通过指针创建
  if result.Error != nil {
    panic(result.Error)
  }
  // 创建后,user.ID 会自动赋值

  // 2. 批量创建
  users := []User{
    {Name: "Alice", Email: "alice@example.com", Age: 25},
    {Name: "Bob", Email: "bob@example.com", Age: 35},
  }
  result = db.Create(&users)
  if result.Error != nil {
    panic(result.Error)
  }
  // 批量创建后,每个user的ID会自动赋值

  // 3. 使用Map创建记录
  result = db.Model(&User{}).Create(map[string]interface{}{
    "Name":  "Charlie",
    "Email": "charlie@example.com",
    "Age":   40,
  })
  if result.Error != nil {
    panic(result.Error)
  }
}

3.2 查询操作与条件构建

package main

import (
  "fmt"
  "gorm.io/driver/sqlite"
  "gorm.io/gorm"
)

type User struct {
  gorm.Model
  Name  string
  Email string
  Age   int
}

func main() {
  db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
  if err != nil {
    panic("failed to connect database")
  }

  // 迁移 schema
  db.AutoMigrate(&User{})

  // 1. 查询单条记录
  var user User
  // 根据主键查询
  result := db.First(&user, 1) // 查询ID=1的记录
  if result.Error != nil {
    panic(result.Error)
  }
  fmt.Printf("Found user: %+v\n", user)

  // 条件查询
  result = db.Where("name = ?", "John Doe").First(&user)
  if result.Error != nil {
    panic(result.Error)
  }

  // 2. 查询多条记录
  var users []User
  // 查询所有记录
  result = db.Find(&users)
  fmt.Printf("Found %d users\n", len(users))

  // 条件查询
  result = db.Where("age > ?", 30).Find(&users)
  fmt.Printf("Found %d users older than 30\n", len(users))

  // IN查询
  result = db.Where("name IN ?", []string{"Alice", "Bob"}).Find(&users)

  // LIKE查询
  result = db.Where("name LIKE ?", "%oh%").Find(&users)

  // 3. 高级条件构建
  result = db.Where(&User{Name: "John", Age: 30}).First(&user) // 结构体条件

  // 链式条件
  result = db.Where("age > ?", 25).Or("name = ?", "Alice").Find(&users)

  // 4. 排序、限制和偏移
  result = db.Order("age desc").Limit(10).Offset(5).Find(&users)

  // 5. 选择特定字段
  result = db.Select("name", "email").Find(&users)
}

3.3 更新与删除操作

package main

import (
  "gorm.io/driver/sqlite"
  "gorm.io/gorm"
)

type User struct {
  gorm.Model
  Name  string
  Email string
  Age   int
}

func main() {
  db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
  if err != nil {
    panic("failed to connect database")
  }

  // 迁移 schema
  db.AutoMigrate(&User{})

  // 更新操作

  // 1. 保存所有字段
  var user User
  db.First(&user, 1)
  user.Name = "John Updated"
  user.Age = 31
  db.Save(&user)

  // 2. 更新单个字段
  db.Model(&User{}).Where("id = ?", 1).Update("name", "John Doe")

  // 3. 更新多个字段
  db.Model(&User{}).Where("id = ?", 1).Updates(User{Name: "John Doe", Age: 32})

  // 4. 使用Map更新多个字段
  db.Model(&User{}).Where("id = ?", 1).Updates(map[string]interface{}{
    "name": "John Doe",
    "age":  33,
  })

  // 5. 更新选定字段(忽略零值)
  db.Model(&User{}).Where("id = ?", 1).Select("name").Updates(User{Name: "John", Age: 0})

  // 删除操作

  // 1. 物理删除
  db.Delete(&User{}, 1) // 删除ID=1的记录

  // 2. 批量物理删除
  db.Where("age < ?", 18).Delete(&User{})

  // 3. 软删除(需要模型有DeletedAt字段)
  db.Delete(&User{}, 1) // 实际上是更新DeletedAt字段

  // 查询时会自动过滤软删除的记录
  var users []User
  db.Find(&users) // 不包含已软删除的记录

  // 查询包括软删除的记录
  db.Unscoped().Find(&users)

  // 永久删除软删除的记录
  db.Unscoped().Delete(&User{}, 1)
}

4. 关联关系处理

GORM支持多种关联关系:一对一、一对多和多对多。

4.1 一对一关联

package main

import (
  "gorm.io/driver/sqlite"
  "gorm.io/gorm"
)

// User 模型
type User struct {
  gorm.Model
  Name      string
  ProfileID uint
  Profile   Profile // 一对一关联
}

// Profile 模型
type Profile struct {
  gorm.Model
  Address string
  Phone   string
  User    User // 反向关联
}

func main() {
  db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
  if err != nil {
    panic("failed to connect database")
  }

  // 迁移 schema
  db.AutoMigrate(&User{}, &Profile{})

  // 创建关联记录
  profile := Profile{Address: "123 Main St", Phone: "555-1234"}
  user := User{Name: "John Doe", Profile: profile}

  // 创建用户时会自动创建关联的profile
  db.Create(&user)

  // 查询关联记录
  var foundUser User
  db.Preload("Profile").First(&foundUser, user.ID)

  // 更新关联记录
  db.Model(&foundUser.Profile).Update("Phone", "555-5678")

  // 删除关联记录(级联删除)
  db.Delete(&foundUser)
  // 如需级联删除,需要设置外键约束
  // db.Model(&User{}).Association("Profile").Delete(&foundUser.Profile)
}

4.2 一对多关联

package main

import (
  "gorm.io/driver/sqlite"
  "gorm.io/gorm"
)

// User 模型
type User struct {
  gorm.Model
  Name  string
  Posts []Post // 一对多关联
}

// Post 模型
type Post struct {
  gorm.Model
  Title  string
  Content string
  UserID uint   // 外键
  User   User   // 反向关联
}

func main() {
  db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
  if err != nil {
    panic("failed to connect database")
  }

  // 迁移 schema
  db.AutoMigrate(&User{}, &Post{})

  // 创建带有关联的记录
  user := User{
    Name: "John Doe",
    Posts: []Post{
      {Title: "First Post", Content: "Hello World"},
      {Title: "Second Post", Content: "GORM is great"},
    },
  }
  db.Create(&user)

  // 查询用户及其所有文章
  var foundUser User
  db.Preload("Posts").First(&foundUser, user.ID)

  // 为用户添加新文章
  newPost := Post{Title: "Third Post", Content: "Learning GORM"}
  db.Model(&foundUser).Association("Posts").Append(&newPost)

  // 查询用户的文章
  var posts []Post
  db.Model(&foundUser).Association("Posts").Find(&posts)

  // 解除用户与某篇文章的关联
  db.Model(&foundUser).Association("Posts").Delete(&posts[0])

  // 清除用户的所有文章关联
  db.Model(&foundUser).Association("Posts").Clear()
}

4.3 多对多关联

package main

import (
  "gorm.io/driver/sqlite"
  "gorm.io/gorm"
)

// User 模型
type User struct {
  gorm.Model
  Name  string
  Roles []Role `gorm:"many2many:user_roles;"` // 多对多关联
}

// Role 模型
type Role struct {
  gorm.Model
  Name  string
  Users []User `gorm:"many2many:user_roles;"` // 反向关联
}

func main() {
  db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
  if err != nil {
    panic("failed to connect database")
  }

  // 迁移 schema
  db.AutoMigrate(&User{}, &Role{})

  // 创建角色
  adminRole := Role{Name: "Admin"}
  userRole := Role{Name: "User"}
  db.Create(&adminRole)
  db.Create(&userRole)

  // 创建用户并关联角色
  user := User{
    Name: "John Doe",
    Roles: []Role{adminRole, userRole},
  }
  db.Create(&user)

  // 查询用户及其角色
  var foundUser User
  db.Preload("Roles").First(&foundUser, user.ID)

  // 为用户添加新角色
  editorRole := Role{Name: "Editor"}
  db.Create(&editorRole)
  db.Model(&foundUser).Association("Roles").Append(&editorRole)

  // 替换用户的角色
  db.Model(&foundUser).Association("Roles").Replace([]Role{userRole, editorRole})

  // 移除用户的某个角色
  db.Model(&foundUser).Association("Roles").Delete(&editorRole)

  // 获取用户的所有角色
  var roles []Role
  db.Model(&foundUser).Association("Roles").Find(&roles)
}

4.4 预加载与延迟加载

package main

import (
  "gorm.io/driver/sqlite"
  "gorm.io/gorm"
)

// 模型定义
type User struct {
  gorm.Model
  Name  string
  Posts []Post
  Profile Profile
}

type Post struct {
  gorm.Model
  Title   string
  Content string
  UserID  uint
  Comments []Comment
}

type Comment struct {
  gorm.Model
  Content string
  PostID  uint
}

type Profile struct {
  gorm.Model
  Address string
  UserID  uint
}

func main() {
  db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
  if err != nil {
    panic("failed to connect database")
  }

  // 迁移 schema
  db.AutoMigrate(&User{}, &Post{}, &Comment{}, &Profile{})

  // 1. 预加载(Eager Loading)
  // 预加载用户的Profile
  var user User
  db.Preload("Profile").First(&user)

  // 预加载用户的Posts
  db.Preload("Posts").First(&user)

  // 预加载嵌套关联(Posts的Comments)
  db.Preload("Posts.Comments").First(&user)

  // 带条件的预加载
  db.Preload("Posts", "title LIKE ?", "%GORM%").First(&user)

  // 2. 自动预加载
  // 可以通过配置全局自动预加载
  db, err = gorm.Open(sqlite.Open("test.db"), &gorm.Config{
    Preload: []string{"Profile", "Posts"},
  })

  // 3. 延迟加载(Lazy Loading)
  // 先查询用户
  db.First(&user)

  // 当访问关联时才会加载
  db.Model(&user).Association("Posts").Find(&user.Posts)
  db.Model(&user).Association("Profile").Find(&user.Profile)
}

5. 高级查询功能

5.1 原生SQL与查询构建器

package main

import (
  "fmt"
  "gorm.io/driver/sqlite"
  "gorm.io/gorm"
)

type User struct {
  gorm.Model
  Name string
  Age  int
}

func main() {
  db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
  if err != nil {
    panic("failed to connect database")
  }

  // 迁移 schema
  db.AutoMigrate(&User{})

  // 1. 原生SQL查询
  var users []User
  // 使用Raw执行原生SQL
  result := db.Raw("SELECT * FROM users WHERE age > ?", 30).Scan(&users)
  if result.Error != nil {
    panic(result.Error)
  }

  // 2. Exec执行原生SQL(更新、删除等)
  result = db.Exec("UPDATE users SET age = ? WHERE name = ?", 31, "John Doe")
  if result.Error != nil {
    panic(result.Error)
  }
  fmt.Printf("Updated %d rows\n", result.RowsAffected)

  // 3. 使用NamedArg
  db.Raw("SELECT * FROM users WHERE name = @name AND age = @age",
    map[string]interface{}{"name": "John Doe", "age": 31}).Scan(&users)

  // 4. 查询构建器
  // 复杂条件查询
  db.Where("age > ?", 25).
    Or("name LIKE ?", "%ohn%").
    Order("age desc").
    Limit(10).
    Offset(0).
    Find(&users)

  // 5. 子查询
  subQuery := db.Model(&User{}).Select("AVG(age)").Where("name LIKE ?", "%ohn%")
  var usersAboveAvg []User
  db.Where("age > (?)", subQuery).Find(&usersAboveAvg)
}

5.2 子查询与联表查询

package main

import (
  "gorm.io/driver/sqlite"
  "gorm.io/gorm"
)

type User struct {
  gorm.Model
  Name  string
  Age   int
  Posts []Post
}

type Post struct {
  gorm.Model
  Title   string
  Content string
  UserID  uint
  Views   int
}

func main() {
  db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
  if err != nil {
    panic("failed to connect database")
  }

  // 迁移 schema
  db.AutoMigrate(&User{}, &Post{})

  // 1. 子查询作为条件
  // 查询发表过至少一篇文章的用户
  subQuery := db.Model(&Post{}).Select("DISTINCT user_id")
  var activeUsers []User
  db.Where("id IN (?)", subQuery).Find(&activeUsers)

  // 2. 子查询作为字段
  // 查询每个用户的文章数量
  type UserWithPostCount struct {
    User
    PostCount int
  }

  var usersWithCount []UserWithPostCount
  db.Model(&User{}).Select("users.*, (SELECT COUNT(*) FROM posts WHERE posts.user_id = users.id) as post_count").
    Find(&usersWithCount)

  // 3. 联表查询
  // INNER JOIN
  var users []User
  db.Joins("JOIN posts ON posts.user_id = users.id").
    Where("posts.views > ?", 100).
    Find(&users)

  // LEFT JOIN
  db.Joins("LEFT JOIN posts ON posts.user_id = users.id").
    Where("posts.id IS NULL"). // 查找没有发表文章的用户
    Find(&users)

  // 4. 复杂联表查询
  type UserPost struct {
    Name    string
    Title   string
    Content string
  }

  var userPosts []UserPost
  db.Table("users").
    Select("users.name, posts.title, posts.content").
    Joins("JOIN posts ON users.id = posts.user_id").
    Where("users.age > ?", 25).
    Scan(&userPosts)
}

5.3 聚合查询与分组

package main

import (
  "fmt"
  "gorm.io/driver/sqlite"
  "gorm.io/gorm"
)

type User struct {
  gorm.Model
  Name  string
  Age   int
  City  string
  Posts []Post
}

type Post struct {
  gorm.Model
  Title   string
  UserID  uint
  Views   int
}

func main() {
  db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
  if err != nil {
    panic("failed to connect database")
  }

  // 迁移 schema
  db.AutoMigrate(&User{}, &Post{})

  // 1. 基本聚合函数
  var count int64
  db.Model(&User{}).Count(&count)
  fmt.Printf("Total users: %d\n", count)

  var avgAge float64
  db.Model(&User{}).Where("city = ?", "New York").Avg("age", &avgAge)
  fmt.Printf("Average age in New York: %.2f\n", avgAge)

  var maxAge int
  db.Model(&User{}).Where("city = ?", "London").Select("MAX(age)").Scan(&maxAge)
  fmt.Printf("Max age in London: %d\n", maxAge)

  // 2. 分组查询
  type CityAge struct {
    City string
    AvgAge float64
    UserCount int64
  }

  var cityAges []CityAge
  db.Model(&User{}).
    Select("city, AVG(age) as avg_age, COUNT(*) as user_count").
    Group("city").
    Having("user_count > ?", 5). // 过滤分组结果
    Scan(&cityAges)

  // 3. 关联聚合
  type UserPostStats struct {
    Name string
    PostCount int64
    TotalViews int64
  }

  var stats []UserPostStats
  db.Model(&User{}).
    Select("users.name, COUNT(posts.id) as post_count, SUM(posts.views) as total_views").
    Joins("LEFT JOIN posts ON users.id = posts.user_id").
    Group("users.id").
    Scan(&stats)
}

6. 钩子函数与回调

GORM提供了生命周期钩子函数,可以在创建、查询、更新、删除等操作前后执行自定义逻辑。

6.1 生命周期钩子

package main

import (
  "gorm.io/driver/sqlite"
  "gorm.io/gorm"
)

type User struct {
  gorm.Model
  Name  string
  Email string
  Age   int
  // 用于演示钩子的字段
  Password string
  Status   string
}

// 钩子方法 - 创建之前
func (u *User) BeforeCreate(tx *gorm.DB) error {
  // 在创建用户前加密密码
  if u.Password != "" {
    // 实际应用中应该使用更安全的加密方式
    tx.Statement.SetColumn("Password", "hashed_"+u.Password)
  }

  // 设置默认状态
  if u.Status == "" {
    u.Status = "active"
  }
  return nil
}

// 钩子方法 - 更新之前
func (u *User) BeforeUpdate(tx *gorm.DB) error {
  // 如果更新了密码,重新加密
  if pw, ok := tx.Statement.Changed("Password"); ok && pw != "" {
    tx.Statement.SetColumn("Password", "hashed_"+pw.(string))
  }
  return nil
}

// 钩子方法 - 删除之前
func (u *User) BeforeDelete(tx *gorm.DB) error {
  // 检查是否允许删除
  if u.Status == "admin" {
    return gorm.ErrInvalidData // 不允许删除管理员
  }
  return nil
}

// 钩子方法 - 查询之后
func (u *User) AfterFind(tx *gorm.DB) error {
  // 查询后处理,例如脱敏
  u.Password = "***"
  return nil
}

func main() {
  db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
  if err != nil {
    panic("failed to connect database")
  }

  // 迁移 schema
  db.AutoMigrate(&User{})

  // 创建用户 - 会触发BeforeCreate钩子
  user := User{Name: "John Doe", Email: "john@example.com", Age: 30, Password: "secret"}
  db.Create(&user)

  // 更新用户 - 会触发BeforeUpdate钩子
  db.Model(&user).Update("Password", "newsecret")

  // 查询用户 - 会触发AfterFind钩子
  var foundUser User
  db.First(&foundUser, user.ID)

  // 删除用户 - 会触发BeforeDelete钩子
  db.Delete(&user)
}

6.2 自定义回调函数

除了模型上的钩子方法,GORM还允许注册全局或会话级别的回调函数:

package main

import (
  "fmt"
  "gorm.io/driver/sqlite"
  "gorm.io/gorm"
  "gorm.io/gorm/schema"
)

type Product struct {
  gorm.Model
  Name  string
  Price float64
  Stock int
}

func main() {
  db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
  if err != nil {
    panic("failed to connect database")
  }

  // 迁移 schema
  db.AutoMigrate(&Product{})

  // 注册全局回调 - 创建前
  db.Callback().Create().Before("gorm:before_create").Register("custom_create_before", func(db *gorm.DB) {
    if p, ok := db.Statement.Model.(*Product); ok {
      fmt.Printf("Creating product: %s\n", p.Name)
      // 设置默认值
      if p.Price <= 0 {
        p.Price = 9.99
      }
    }
  })

  // 注册全局回调 - 创建后
  db.Callback().Create().After("gorm:after_create").Register("custom_create_after", func(db *gorm.DB) {
    if p, ok := db.Statement.Model.(*Product); ok {
      fmt.Printf("Created product with ID: %d\n", p.ID)
      // 可以在这里记录日志或发送通知
    }
  })

  // 注册更新回调
  db.Callback().Update().Before("gorm:before_update").Register("custom_update_before", func(db *gorm.DB) {
    if _, ok := db.Statement.Model.(*Product); ok {
      // 检查库存是否足够
      if stock, ok := db.Statement.Changed("Stock"); ok {
        if stock.(int) < 0 {
          db.AddError(fmt.Errorf("stock cannot be negative"))
        }
      }
    }
  })

  // 使用带回调的数据库操作
  product := Product{Name: "Laptop", Price: 999.99, Stock: 10}
  db.Create(&product)

  // 尝试设置负库存,会被回调阻止
  db.Model(&product).Update("Stock", -5)
  if err := db.Error; err != nil {
    fmt.Println("Error:", err)
  }
}

6.3 插件机制应用

GORM的插件机制允许扩展其功能,以下是一个简单的插件示例:

package main

import (
  "context"
  "fmt"
  "time"
  "gorm.io/driver/sqlite"
  "gorm.io/gorm"
)

// 定义一个简单的日志插件
type LoggerPlugin struct{}

// Name 插件名称
func (l *LoggerPlugin) Name() string {
  return "logger_plugin"
}

// Initialize 初始化插件
func (l *LoggerPlugin) Initialize(db *gorm.DB) error {
  // 注册回调
  db.Callback().Create().Before("gorm:before_create").Register("logger_plugin:create_before", l.logBefore)
  db.Callback().Create().After("gorm:after_create").Register("logger_plugin:create_after", l.logAfter)

  db.Callback().Update().Before("gorm:before_update").Register("logger_plugin:update_before", l.logBefore)
  db.Callback().Update().After("gorm:after_update").Register("logger_plugin:update_after", l.logAfter)

  return nil
}

// logBefore 记录操作前的日志
func (l *LoggerPlugin) logBefore(db *gorm.DB) {
  start := time.Now()
  db.Set("start_time", start)

  fmt.Printf("Starting %s operation on %s\n", 
    db.Statement.Operation, 
    db.Statement.Table)
}

// logAfter 记录操作后的日志
func (l *LoggerPlugin) logAfter(db *gorm.DB) {
  start, _ := db.Get("start_time")
  duration := time.Since(start.(time.Time))

  fmt.Printf("Completed %s operation on %s in %v. Rows affected: %d\n",
    db.Statement.Operation,
    db.Statement.Table,
    duration,
    db.Statement.RowsAffected)
}

// 模型定义
type Product struct {
  gorm.Model
  Name  string
  Price float64
}

func main() {
  db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
  if err != nil {
    panic("failed to connect database")
  }

  // 注册插件
  db.Use(&LoggerPlugin{})

  // 迁移 schema
  db.AutoMigrate(&Product{})

  // 执行数据库操作,插件会自动记录日志
  product := Product{Name: "Phone", Price: 499.99}
  db.Create(&product)

  db.Model(&product).Update("Price", 599.99)
}

7. 性能优化实践

7.1 N+1查询问题解决

N+1查询问题是指当查询N条记录时,会额外执行N次查询来获取关联数据,导致性能问题。GORM的预加载功能可以解决这个问题。

package main

import (
  "time"
  "gorm.io/driver/sqlite"
  "gorm.io/gorm"
)

// 模型定义
type Author struct {
  gorm.Model
  Name  string
  Books []Book
}

type Book struct {
  gorm.Model
  Title    string
  AuthorID uint
  Author   Author
}

func main() {
  db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
  if err != nil {
    panic("failed to connect database")
  }

  // 迁移 schema
  db.AutoMigrate(&Author{}, &Book{})

  // 演示N+1问题
  fmt.Println("=== N+1 Query Problem ===")
  start := time.Now()

  // 1次查询获取所有作者
  var authors []Author
  db.Find(&authors)

  // 对每个作者执行1次查询获取书籍 - N次查询
  for i := range authors {
    db.Find(&authors[i].Books, "author_id = ?", authors[i].ID)
  }

  fmt.Printf("N+1查询耗时: %v\n", time.Since(start))

  // 使用预加载解决N+1问题
  fmt.Println("\n=== Using Preload to Solve N+1 ===")
  start = time.Now()

  // 仅需2次查询:1次查询作者,1次查询所有相关书籍
  var authorsWithPreload []Author
  db.Preload("Books").Find(&authorsWithPreload)

  fmt.Printf("预加载查询耗时: %v\n", time.Since(start))

  // 嵌套预加载(如果有更深层次的关联)
  // db.Preload("Books.Chapters").Find(&authorsWithPreload)

  // 条件预加载
  fmt.Println("\n=== Conditional Preload ===")
  var authorsWithFilteredBooks []Author
  db.Preload("Books", "title LIKE ?", "%Go%").Find(&authorsWithFilteredBooks)
}

7.2 批量操作优化

批量操作可以减少数据库交互次数,显著提高性能:

package main

import (
  "time"
  "gorm.io/driver/sqlite"
  "gorm.io/gorm"
)

// 模型定义
type Product struct {
  gorm.Model
  Name  string
  Price float64
  Stock int
}

func main() {
  db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
  if err != nil {
    panic("failed to connect database")
  }

  // 迁移 schema
  db.AutoMigrate(&Product{})

  // 1. 批量创建
  fmt.Println("=== Batch Create ===")
  start := time.Now()

  // 单个创建 - 100次数据库交互
  for i := 0; i < 100; i++ {
    product := Product{Name: fmt.Sprintf("Product %d", i), Price: float64(10 + i%50), Stock: 100}
    db.Create(&product)
  }

  fmt.Printf("单个创建耗时: %v\n", time.Since(start))

  // 批量创建 - 1次数据库交互
  start = time.Now()
  var products []Product
  for i := 100; i < 200; i++ {
    products = append(products, Product{Name: fmt.Sprintf("Product %d", i), Price: float64(10 + i%50), Stock: 100})
  }
  db.Create(&products)

  fmt.Printf("批量创建耗时: %v\n", time.Since(start))

  // 2. 批量更新
  fmt.Println("\n=== Batch Update ===")

  // 批量更新所有符合条件的记录
  result := db.Model(&Product{}).Where("price < ?", 30).Update("stock", 50)
  fmt.Printf("批量更新了 %d 条记录\n", result.RowsAffected)

  // 批量更新不同的值
  type UpdateData struct {
    ID    uint
    Price float64
  }

  updates := []UpdateData{
    {ID: 1, Price: 19.99},
    {ID: 2, Price: 29.99},
    {ID: 3, Price: 39.99},
  }

  db.Model(&Product{}).CreateInBatches(updates, len(updates))

  // 3. 批量删除
  fmt.Println("\n=== Batch Delete ===")
  result = db.Where("price > ?", 50).Delete(&Product{})
  fmt.Printf("批量删除了 %d 条记录\n", result.RowsAffected)
}

7.3 索引与查询优化

合理的索引设计和查询优化可以显著提升数据库性能:

package main

import (
  "time"
  "gorm.io/driver/sqlite"
  "gorm.io/gorm"
)

// 带索引的模型定义
type Product struct {
  gorm.Model
  Name      string  `gorm:"index:idx_name"` // 普通索引
  SKU       string  `gorm:"uniqueIndex:idx_sku"` // 唯一索引
  Category  string  `gorm:"index:idx_category_price"` // 复合索引的一部分
  Price     float64 `gorm:"index:idx_category_price"` // 复合索引的一部分
  CreatedAt time.Time `gorm:"index:idx_created_at"` // 时间索引
}

func main() {
  db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
  if err != nil {
    panic("failed to connect database")
  }

  // 迁移 schema - 会自动创建定义的索引
  db.AutoMigrate(&Product{})

  // 插入测试数据
  var products []Product
  for i := 0; i < 1000; i++ {
    category := "category" + fmt.Sprintf("%d", i%10)
    products = append(products, Product{
      Name:     "product" + fmt.Sprintf("%d", i),
      SKU:      "sku" + fmt.Sprintf("%04d", i),
      Category: category,
      Price:    float64(10 + i%90),
    })
  }
  db.Create(&products)

  // 查询优化示例

  // 1. 使用索引字段进行过滤
  fmt.Println("=== Using Indexed Fields ===")
  var result Product

  // 不使用索引的查询
  start := time.Now()
  db.Where("name = ?", "product500").First(&result)
  fmt.Printf("查询耗时 (无索引优化): %v\n", time.Since(start))

  // 使用索引的查询
  start = time.Now()
  db.Where("sku = ?", "sku0500").First(&result)
  fmt.Printf("查询耗时 (使用唯一索引): %v\n", time.Since(start))

  // 2. 复合索引查询
  fmt.Println("\n=== Composite Index Query ===")
  var categoryProducts []Product

  start = time.Now()
  db.Where("category = ? AND price < ?", "category5", 50).Find(&categoryProducts)
  fmt.Printf("复合索引查询耗时: %v\n", time.Since(start))
  fmt.Printf("查询结果数量: %d\n", len(categoryProducts))

  // 3. 只查询需要的字段
  fmt.Println("\n=== Select Only Needed Fields ===")
  type ProductNamePrice struct {
    Name  string
    Price float64
  }

  var namePrices []ProductNamePrice
  start = time.Now()
  db.Model(&Product{}).Select("name", "price").Where("category = ?", "category3").Find(&namePrices)
  fmt.Printf("只查询必要字段耗时: %v\n", time.Since(start))

  // 4. 分页查询
  fmt.Println("\n=== Pagination ===")
  var paginatedProducts []Product
  pageSize := 20
  page := 3

  start = time.Now()
  db.Limit(pageSize).Offset((page - 1) * pageSize).Find(&paginatedProducts)
  fmt.Printf("分页查询耗时: %v\n", time.Since(start))
  fmt.Printf("获取记录数量: %d\n", len(paginatedProducts))
}

实战练习

练习1:博客系统的数据模型设计

设计一个简单的博客系统数据模型,包括用户、文章、评论和分类,并实现基本的CRUD操作。

要求: 1. 设计模型结构,包括适当的关联关系 2. 为模型添加合适的索引 3. 实现创建、查询、更新和删除操作 4. 使用钩子函数实现密码加密和内容验证

示例代码框架:

package main

import (
  "gorm.io/driver/sqlite"
  "gorm.io/gorm"
)

// 在这里定义你的模型
// User, Post, Comment, Category

func main() {
  // 连接数据库

  // 迁移模型

  // 实现CRUD操作
}

练习2:复杂关联查询实现

基于练习1的博客系统模型,实现以下复杂查询:

  1. 查询所有文章,包含作者信息、评论数量和分类信息
  2. 查询特定分类下的所有文章,按发布时间排序
  3. 统计每个作者的文章数量和平均评论数
  4. 查找评论数最多的前10篇文章

要求:使用预加载、联表查询和聚合函数来实现,并注意避免N+1查询问题。

练习3:GORM性能调优实战

针对练习1和练习2实现的功能,进行性能优化:

  1. 分析现有查询的性能瓶颈
  2. 添加适当的索引提升查询性能
  3. 将多次单条操作改为批量操作
  4. 实现查询缓存机制
  5. 对比优化前后的性能差异

要求:记录优化过程和性能提升数据,编写性能测试代码。

通过这些练习,你将能够深入理解GORM框架的使用,并掌握在实际项目中应用GORM的最佳实践。


详细内容待补充...