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(0, 0, -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(1, 10),
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 {
returnnil, nil
}
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 核心要点
|
|
|
|
|---|---|---|
| 钩子 |
|
|
| 作用域 |
|
|
| 自定义类型 |
|
|
| 插件 |
|
|
6.2 最佳实践
✅ 合理使用钩子函数
✅ 用作用域复用查询逻辑
✅ 自定义类型处理特殊数据
✅ 开发插件扩展功能
✅ 注意钩子函数的性能影响
✅ 避免在钩子中执行耗时操作
下一篇预告
《GORM关联查询:一对多、多对多关系处理》
💡 GORM高级特性让ORM更强大,钩子、作用域、插件助力复杂场景!
夜雨聆风