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的博客系统模型,实现以下复杂查询:
- 查询所有文章,包含作者信息、评论数量和分类信息
- 查询特定分类下的所有文章,按发布时间排序
- 统计每个作者的文章数量和平均评论数
- 查找评论数最多的前10篇文章
要求:使用预加载、联表查询和聚合函数来实现,并注意避免N+1查询问题。
练习3:GORM性能调优实战¶
针对练习1和练习2实现的功能,进行性能优化:
- 分析现有查询的性能瓶颈
- 添加适当的索引提升查询性能
- 将多次单条操作改为批量操作
- 实现查询缓存机制
- 对比优化前后的性能差异
要求:记录优化过程和性能提升数据,编写性能测试代码。
通过这些练习,你将能够深入理解GORM框架的使用,并掌握在实际项目中应用GORM的最佳实践。
详细内容待补充...