乐于分享
好东西不私藏

GORM高级特性:钩子、作用域、插件开发

GORM高级特性:钩子、作用域、插件开发

掌握GORM的高级特性,能让你的数据库操作更加灵活和强大。本文将深入讲解GORM的钩子函数、作用域、插件开发等高级功能,助你成为GORM高手。

一、钩子函数(Hooks)

1.1 钩子函数概述

┌─────────────────────────────────────────┐
│         GORM钩子函数执行顺序            │
├─────────────────────────────────────────┤
│ 创建:BeforeSave → BeforeCreate →       │
│       AfterCreate → AfterSave           │
│                                         │
│ 更新:BeforeSave → BeforeUpdate →       │
│       AfterUpdate → AfterSave           │
│                                         │
│ 删除:BeforeDelete → AfterDelete        │
│                                         │
│ 查询:AfterFind                         │
└─────────────────────────────────────────┘

1.2 创建钩子

type User struct {
    ID        uint
    Username  string
    Password  string
    CreatedAt time.Time
    UpdatedAt time.Time
}

// BeforeCreate - 创建前钩子
func(u *User)BeforeCreate(tx *gorm.DB)error {
// 密码加密
    hashedPassword, err := bcrypt.GenerateFromPassword(
        []byte(u.Password), 
        bcrypt.DefaultCost,
    )
if err != nil {
return err
    }
    u.Password = string(hashedPassword)

    fmt.Println("✅ BeforeCreate: 密码已加密")
returnnil
}

// AfterCreate - 创建后钩子
func(u *User)AfterCreate(tx *gorm.DB)error {
// 发送欢迎邮件
    fmt.Printf("✅ AfterCreate: 发送欢迎邮件给 %s\n", u.Username)

// 创建用户配置
    config := UserConfig{
        UserID: u.ID,
        Theme:  "default",
    }
return tx.Create(&config).Error
}

// BeforeSave - 保存前钩子(创建和更新都会触发)
func(u *User)BeforeSave(tx *gorm.DB)error {
// 数据验证
iflen(u.Username) < 3 {
return errors.New("用户名长度不能小于3")
    }

    fmt.Println("✅ BeforeSave: 数据验证通过")
returnnil
}

// AfterSave - 保存后钩子
func(u *User)AfterSave(tx *gorm.DB)error {
// 清除缓存
    fmt.Printf("✅ AfterSave: 清除用户 %d 的缓存\n", u.ID)
returnnil
}

1.3 更新钩子

// BeforeUpdate - 更新前钩子
func(u *User)BeforeUpdate(tx *gorm.DB)error {
// 检查是否修改了敏感字段
if tx.Statement.Changed("Username") {
        fmt.Println("⚠️ BeforeUpdate: 用户名被修改")

// 记录修改日志
        log := ChangeLog{
            UserID:    u.ID,
            Field:     "username",
            OldValue:  tx.Statement.GetField("Username"),
            NewValue:  u.Username,
            ChangedAt: time.Now(),
        }
return tx.Create(&log).Error
    }

returnnil
}

// AfterUpdate - 更新后钩子
func(u *User)AfterUpdate(tx *gorm.DB)error {
// 发送通知
    fmt.Printf("✅ AfterUpdate: 用户 %d 信息已更新\n", u.ID)

// 更新缓存
    cache.Set(fmt.Sprintf("user:%d", u.ID), u, 10*time.Minute)

returnnil
}

1.4 删除钩子

// BeforeDelete - 删除前钩子
func(u *User)BeforeDelete(tx *gorm.DB)error {
// 检查是否有关联数据
var count int64
    tx.Model(&Order{}).Where("user_id = ?", u.ID).Count(&count)

if count > 0 {
return errors.New("用户还有未完成的订单,无法删除")
    }

    fmt.Printf("✅ BeforeDelete: 删除前检查通过\n")
returnnil
}

// AfterDelete - 删除后钩子
func(u *User)AfterDelete(tx *gorm.DB)error {
// 删除关联数据
    tx.Where("user_id = ?", u.ID).Delete(&UserConfig{})

// 清除缓存
    cache.Delete(fmt.Sprintf("user:%d", u.ID))

    fmt.Printf("✅ AfterDelete: 清理完成\n")
returnnil
}

1.5 查询钩子

// AfterFind - 查询后钩子
func(u *User)AfterFind(tx *gorm.DB)error {
// 隐藏敏感信息
    u.Password = "******"

// 加载额外数据
    fmt.Printf("✅ AfterFind: 查询到用户 %s\n", u.Username)

returnnil
}

二、作用域(Scopes)

2.1 定义作用域

// 活跃用户作用域
funcActiveUsers(db *gorm.DB) *gorm.DB {
return db.Where("status = ?"1)
}

// 成年用户作用域
funcAdultUsers(db *gorm.DB) *gorm.DB {
return db.Where("age >= ?"18)
}

// 最近注册作用域
funcRecentUsers(days int)func(db *gorm.DB) *gorm.DB {
returnfunc(db *gorm.DB) *gorm.DB {
return db.Where("created_at > ?", time.Now().AddDate(00, -days))
    }
}

// 分页作用域
funcPaginate(page, pageSize int)func(db *gorm.DB) *gorm.DB {
returnfunc(db *gorm.DB) *gorm.DB {
        offset := (page - 1) * pageSize
return db.Offset(offset).Limit(pageSize)
    }
}

// 排序作用域
funcOrderByCreated(desc bool)func(db *gorm.DB) *gorm.DB {
returnfunc(db *gorm.DB) *gorm.DB {
if desc {
return db.Order("created_at DESC")
        }
return db.Order("created_at ASC")
    }
}

2.2 使用作用域

funcmain() {
    db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})

var users []User

// 使用单个作用域
    db.Scopes(ActiveUsers).Find(&users)

// 组合多个作用域
    db.Scopes(ActiveUsers, AdultUsers).Find(&users)

// 使用带参数的作用域
    db.Scopes(
        ActiveUsers,
        RecentUsers(7),
        Paginate(110),
        OrderByCreated(true),
    ).Find(&users)

    fmt.Printf("查询到 %d 个用户\n"len(users))
}

2.3 复杂作用域示例

// 搜索作用域
funcSearch(keyword string)func(db *gorm.DB) *gorm.DB {
returnfunc(db *gorm.DB) *gorm.DB {
if keyword == "" {
return db
        }

        pattern := "%" + keyword + "%"
return db.Where("username LIKE ? OR email LIKE ?", pattern, pattern)
    }
}

// 日期范围作用域
funcDateRange(start, end time.Time)func(db *gorm.DB) *gorm.DB {
returnfunc(db *gorm.DB) *gorm.DB {
if !start.IsZero() {
            db = db.Where("created_at >= ?", start)
        }
if !end.IsZero() {
            db = db.Where("created_at <= ?", end)
        }
return db
    }
}

// 动态过滤作用域
funcDynamicFilter(filters map[string]interface{})func(db *gorm.DB) *gorm.DB {
returnfunc(db *gorm.DB) *gorm.DB {
for key, value := range filters {
if value != nil && value != "" {
                db = db.Where(key+" = ?", value)
            }
        }
return db
    }
}

// 使用示例
funcQueryUsers(keyword string, page int, filters map[string]interface{}) []User {
var users []User

    db.Scopes(
        Search(keyword),
        DynamicFilter(filters),
        Paginate(page, 20),
        OrderByCreated(true),
    ).Find(&users)

return users
}

三、自定义数据类型

3.1 实现Scanner和Valuer接口

// JSON类型
type JSON json.RawMessage

// Scan - 从数据库读取
func(j *JSON)Scan(value interface{})error {
    bytes, ok := value.([]byte)
if !ok {
return errors.New("类型断言失败")
    }

    *j = JSON(bytes)
returnnil
}

// Value - 写入数据库
func(j JSON)Value()(driver.Value, error) {
iflen(j) == 0 {
returnnilnil
    }
return []byte(j), nil
}

// 使用JSON类型
type Product struct {
    ID     uint
    Name   string
    Attrs  JSON `gorm:"type:json"`// 存储JSON数据
}

funcmain() {
    product := Product{
        Name: "手机",
        Attrs: JSON(`{"color":"black","storage":"128GB"}`),
    }

    db.Create(&product)
}

3.2 加密字段

// 加密字符串类型
type EncryptedString string

func(es *EncryptedString)Scan(value interface{})error {
    bytes, ok := value.([]byte)
if !ok {
return errors.New("类型断言失败")
    }

// 解密
    decrypted, err := decrypt(bytes)
if err != nil {
return err
    }

    *es = EncryptedString(decrypted)
returnnil
}

func(es EncryptedString)Value()(driver.Value, error) {
// 加密
    encrypted, err := encrypt([]byte(es))
if err != nil {
returnnil, err
    }
return encrypted, nil
}

// 使用加密字段
type User struct {
    ID       uint
    Username string
    Phone    EncryptedString `gorm:"type:varchar(255)"`// 加密存储
}

四、插件开发

4.1 创建插件

// 审计日志插件
type AuditLogPlugin struct{}

func(p *AuditLogPlugin)Name()string {
return"AuditLogPlugin"
}

func(p *AuditLogPlugin)Initialize(db *gorm.DB)error {
// 注册回调
    callback := db.Callback()

// 创建后记录日志
    callback.Create().After("gorm:after_create").Register("audit:after_create", p.afterCreate)

// 更新后记录日志
    callback.Update().After("gorm:after_update").Register("audit:after_update", p.afterUpdate)

// 删除后记录日志
    callback.Delete().After("gorm:after_delete").Register("audit:after_delete", p.afterDelete)

returnnil
}

func(p *AuditLogPlugin)afterCreate(db *gorm.DB) {
if db.Error == nil {
        log.Printf("创建记录: %s, ID: %v\n"
            db.Statement.Table, 
            db.Statement.Dest)
    }
}

func(p *AuditLogPlugin)afterUpdate(db *gorm.DB) {
if db.Error == nil {
        log.Printf("更新记录: %s, 影响行数: %d\n"
            db.Statement.Table, 
            db.RowsAffected)
    }
}

func(p *AuditLogPlugin)afterDelete(db *gorm.DB) {
if db.Error == nil {
        log.Printf("删除记录: %s, 影响行数: %d\n"
            db.Statement.Table, 
            db.RowsAffected)
    }
}

// 使用插件
funcmain() {
    db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})

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

// 后续操作会自动记录日志
    db.Create(&User{Username: "test"})
}

4.2 性能监控插件

// 性能监控插件
type PerformancePlugin struct {
    slowThreshold time.Duration
}

funcNewPerformancePlugin(threshold time.Duration) *PerformancePlugin {
return &PerformancePlugin{
        slowThreshold: threshold,
    }
}

func(p *PerformancePlugin)Name()string {
return"PerformancePlugin"
}

func(p *PerformancePlugin)Initialize(db *gorm.DB)error {
    callback := db.Callback()

// 查询前记录开始时间
    callback.Query().Before("gorm:query").Register("performance:before_query", p.beforeQuery)

// 查询后检查耗时
    callback.Query().After("gorm:after_query").Register("performance:after_query", p.afterQuery)

returnnil
}

func(p *PerformancePlugin)beforeQuery(db *gorm.DB) {
    db.InstanceSet("start_time", time.Now())
}

func(p *PerformancePlugin)afterQuery(db *gorm.DB) {
if startTime, ok := db.InstanceGet("start_time"); ok {
        elapsed := time.Since(startTime.(time.Time))

if elapsed > p.slowThreshold {
            log.Printf("⚠️ 慢查询: %s, 耗时: %v\n"
                db.Statement.SQL.String(), 
                elapsed)
        }
    }
}

五、实战案例

5.1 软删除增强

type User struct {
    ID        uint
    Username  string
    DeletedAt gorm.DeletedAt
    DeletedBy uint// 记录删除人
}

func(u *User)BeforeDelete(tx *gorm.DB)error {
// 从上下文获取当前用户ID
if userID, ok := tx.Statement.Context.Value("user_id").(uint); ok {
return tx.Model(u).Update("deleted_by", userID).Error
    }
returnnil
}

// 使用
funcDeleteUser(db *gorm.DB, id, currentUserID uint)error {
    ctx := context.WithValue(context.Background(), "user_id", currentUserID)
return db.WithContext(ctx).Delete(&User{}, id).Error
}

5.2 乐观锁实现

type Product struct {
    ID      uint
    Name    string
    Stock   int
    Version int`gorm:"default:0"`// 版本号
}

func(p *Product)BeforeUpdate(tx *gorm.DB)error {
// 增加版本号
    tx.Statement.SetColumn("Version", gorm.Expr("version + ?"1))

// 添加版本检查条件
    tx.Statement.AddClause(clause.Where{
        Exprs: []clause.Expression{
            clause.Eq{Column: "version", Value: p.Version},
        },
    })

returnnil
}

// 使用乐观锁更新库存
funcUpdateStock(db *gorm.DB, productID uint, quantity int)error {
var product Product

// 查询商品
if err := db.First(&product, productID).Error; err != nil {
return err
    }

// 更新库存
    product.Stock -= quantity
    result := db.Save(&product)

if result.RowsAffected == 0 {
return errors.New("更新失败,数据已被修改")
    }

return result.Error
}

六、总结

6.1 核心要点

特性
用途
场景
钩子
自动化处理
密码加密、日志记录
作用域
复用查询逻辑
分页、过滤、排序
自定义类型
特殊数据处理
JSON、加密字段
插件
扩展功能
审计、监控、缓存

6.2 最佳实践

✅ 合理使用钩子函数
✅ 用作用域复用查询逻辑
✅ 自定义类型处理特殊数据
✅ 开发插件扩展功能
✅ 注意钩子函数的性能影响
✅ 避免在钩子中执行耗时操作

下一篇预告

《GORM关联查询:一对多、多对多关系处理》


💡 GORM高级特性让ORM更强大,钩子、作用域、插件助力复杂场景!