3月7日 19:44
How do Hooks work in GORM?
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.
gotype 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
- BeforeCreate
- BeforeSave
- Execute INSERT
- AfterSave
- AfterCreate
Update Operation
- BeforeUpdate
- BeforeSave
- Execute UPDATE
- AfterSave
- AfterUpdate
Delete Operation
- BeforeDelete
- Execute DELETE
- AfterDelete
Query Operation
- BeforeQuery
- Execute SELECT
- AfterQuery / AfterFind
Practical Use Cases
1. Data Validation
gofunc (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
gofunc (u *User) BeforeCreate(tx *gorm.DB) error { if u.ID == 0 { u.ID = generateUUID() } return nil }
3. Data Encryption
gofunc (u *User) BeforeSave(tx *gorm.DB) error { if u.Password != "" { u.Password = hashPassword(u.Password) } return nil }
4. Timestamp Management
gofunc (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
gofunc (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
gofunc (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:
gofunc (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:
gofunc (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
- Performance Impact: Hooks add operation overhead, avoid time-consuming operations in hooks
- Transaction Consistency: Operations in hooks are in the same transaction as the main operation, handle errors carefully
- Avoid Loops: Don't trigger operations that could cause infinite loops in hooks
- Error Handling: Hooks returning errors will prevent operations, ensure proper error handling
- Batch Operations: Hooks execute for each record in batch operations, pay attention to performance
- Test Coverage: Hook logic needs comprehensive unit test coverage
Best Practices
- Keep Simple: Hook logic should be simple and clear, avoid complex business logic
- Single Responsibility: Each hook should do only one thing
- Logging: Add appropriate logging in hooks
- Error Messages: Provide clear error messages for easier debugging
- Documentation: Add comments for complex hook logic