乐闻世界logo
搜索文章和话题

How do Hooks work in GORM?

3月7日 19:44

GORM's Hooks mechanism allows executing custom logic at different stages of database operations. Hook functions are automatically called before or after specific operations.

Hook Types

Object-level Hooks

These hooks are triggered at the object level and apply to operations on single objects.

go
type User struct { gorm.Model Name string Email string Age int } // Before create hook func (u *User) BeforeCreate(tx *gorm.DB) error { fmt.Println("BeforeCreate: Preparing to create user") if u.Name == "" { return errors.New("Username cannot be empty") } return nil } // After create hook func (u *User) AfterCreate(tx *gorm.DB) error { fmt.Println("AfterCreate: User created successfully") return nil } // Before update hook func (u *User) BeforeUpdate(tx *gorm.DB) error { fmt.Println("BeforeUpdate: Preparing to update user") return nil } // After update hook func (u *User) AfterUpdate(tx *gorm.DB) error { fmt.Println("AfterUpdate: User updated successfully") return nil } // Before save hook (triggers for both Create and Update) func (u *User) BeforeSave(tx *gorm.DB) error { fmt.Println("BeforeSave: Preparing to save user") return nil } // After save hook (triggers for both Create and Update) func (u *User) AfterSave(tx *gorm.DB) error { fmt.Println("AfterSave: User saved successfully") return nil } // Before delete hook func (u *User) BeforeDelete(tx *gorm.DB) error { fmt.Println("BeforeDelete: Preparing to delete user") return nil } // After delete hook func (u *User) AfterDelete(tx *gorm.DB) error { fmt.Println("AfterDelete: User deleted successfully") return nil } // After find hook func (u *User) AfterFind(tx *gorm.DB) error { fmt.Println("AfterFind: User queried successfully") return nil }

Query-level Hooks

These hooks are triggered at the query level and apply to batch operations.

go
// Before query hook func (u *User) BeforeQuery(tx *gorm.DB) error { fmt.Println("BeforeQuery: Preparing to query user") return nil } // After query hook func (u *User) AfterQuery(tx *gorm.DB) error { fmt.Println("AfterQuery: User queried successfully") return nil }

Hook Execution Order

Create Operation

  1. BeforeCreate
  2. BeforeSave
  3. Execute INSERT
  4. AfterSave
  5. AfterCreate

Update Operation

  1. BeforeUpdate
  2. BeforeSave
  3. Execute UPDATE
  4. AfterSave
  5. AfterUpdate

Delete Operation

  1. BeforeDelete
  2. Execute DELETE
  3. AfterDelete

Query Operation

  1. BeforeQuery
  2. Execute SELECT
  3. AfterQuery / AfterFind

Practical Use Cases

1. Data Validation

go
func (u *User) BeforeCreate(tx *gorm.DB) error { if u.Age < 0 { return errors.New("Age cannot be negative") } if !strings.Contains(u.Email, "@") { return errors.New("Invalid email format") } return nil }

2. Auto-generate Fields

go
func (u *User) BeforeCreate(tx *gorm.DB) error { if u.ID == 0 { u.ID = generateUUID() } return nil }

3. Data Encryption

go
func (u *User) BeforeSave(tx *gorm.DB) error { if u.Password != "" { u.Password = hashPassword(u.Password) } return nil }

4. Timestamp Management

go
func (u *User) BeforeCreate(tx *gorm.DB) error { now := time.Now() u.CreatedAt = now u.UpdatedAt = now return nil } func (u *User) BeforeUpdate(tx *gorm.DB) error { u.UpdatedAt = time.Now() return nil }

5. Audit Logging

go
func (u *User) AfterCreate(tx *gorm.DB) error { log.Printf("User created: ID=%d, Name=%s", u.ID, u.Name) return nil } func (u *User) AfterUpdate(tx *gorm.DB) error { log.Printf("User updated: ID=%d, Name=%s", u.ID, u.Name) return nil } func (u *User) AfterDelete(tx *gorm.DB) error { log.Printf("User deleted: ID=%d", u.ID) return nil }

6. Soft Delete Handling

go
func (u *User) BeforeDelete(tx *gorm.DB) error { // Update deletion time on soft delete if tx.Statement.Unscoped { // Real delete return nil } // Soft delete, update DeletedAt return nil }

Transaction Operations in Hooks

You can access transaction context in hooks:

go
func (u *User) AfterCreate(tx *gorm.DB) error { // Create associated record in the same transaction profile := Profile{ UserID: u.ID, Bio: "New user", } return tx.Create(&profile).Error }

Skipping Hooks

Sometimes you need to skip hook execution:

go
// Skip all hooks db.Session(&gorm.Session{SkipHooks: true}).Create(&user) // Use Unscoped to skip soft delete hooks db.Unscoped().Delete(&user)

Hook Return Errors

Returning an error from a hook will prevent the operation from continuing:

go
func (u *User) BeforeCreate(tx *gorm.DB) error { if u.Name == "admin" { return errors.New("Creating admin user is not allowed") } return nil } // Usage err := db.Create(&user).Error if err != nil { fmt.Println("Create failed:", err) }

Notes

  1. Performance Impact: Hooks add operation overhead, avoid time-consuming operations in hooks
  2. Transaction Consistency: Operations in hooks are in the same transaction as the main operation, handle errors carefully
  3. Avoid Loops: Don't trigger operations that could cause infinite loops in hooks
  4. Error Handling: Hooks returning errors will prevent operations, ensure proper error handling
  5. Batch Operations: Hooks execute for each record in batch operations, pay attention to performance
  6. Test Coverage: Hook logic needs comprehensive unit test coverage

Best Practices

  1. Keep Simple: Hook logic should be simple and clear, avoid complex business logic
  2. Single Responsibility: Each hook should do only one thing
  3. Logging: Add appropriate logging in hooks
  4. Error Messages: Provide clear error messages for easier debugging
  5. Documentation: Add comments for complex hook logic
标签:Gorm