3月6日 21:37
How to handle errors in GORM?
GORM provides comprehensive error handling mechanisms. Proper error handling is crucial for building stable applications.
Error Handling Basics
Check Errors
All GORM operations return errors, need to check db.Error:
go// Create record if err := db.Create(&user).Error; err != nil { log.Printf("Failed to create user: %v", err) return err } // Query record if err := db.First(&user, 1).Error; err != nil { log.Printf("Failed to query user: %v", err) return err } // Update record if err := db.Model(&user).Update("name", "John").Error; err != nil { log.Printf("Failed to update user: %v", err) return err } // Delete record if err := db.Delete(&user).Error; err != nil { log.Printf("Failed to delete user: %v", err) return err }
Common Error Types
1. Record Not Found Error
govar user User result := db.First(&user, 999) if errors.Is(result.Error, gorm.ErrRecordNotFound) { log.Println("User does not exist") // Handle record not found case } else if result.Error != nil { log.Printf("Query failed: %v", result.Error) return result.Error }
2. Connection Error
godb, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { log.Printf("Database connection failed: %v", err) panic(err) } // Test connection sqlDB, err := db.DB() if err != nil { log.Printf("Failed to get database connection: %v", err) panic(err) } if err := sqlDB.Ping(); err != nil { log.Printf("Database ping failed: %v", err) panic(err) }
3. Constraint Error
go// Unique constraint conflict user := User{Email: "existing@example.com"} if err := db.Create(&user).Error; err != nil { if strings.Contains(err.Error(), "Duplicate entry") { log.Println("Email already exists") return errors.New("Email already exists") } return err } // Foreign key constraint error if err := db.Create(&order).Error; err != nil { if strings.Contains(err.Error(), "foreign key constraint") { log.Println("User does not exist") return errors.New("User does not exist") } return err }
4. Validation Error
go// Use hooks for validation func (u *User) BeforeCreate(tx *gorm.DB) error { if u.Name == "" { return errors.New("Username cannot be empty") } if !strings.Contains(u.Email, "@") { return errors.New("Invalid email format") } return nil } // Handle validation error when creating user := User{Name: "", Email: "invalid"} if err := db.Create(&user).Error; err != nil { log.Printf("Validation failed: %v", err) return err }
Error Handling Best Practices
1. Use Transactions for Error Handling
goerr := db.Transaction(func(tx *gorm.DB) error { if err := tx.Create(&user).Error; err != nil { return err // Auto rollback } if err := tx.Create(&profile).Error; err != nil { return err // Auto rollback } return nil // Auto commit }) if err != nil { log.Printf("Transaction failed: %v", err) return err }
2. Custom Error Handling
gotype DBError struct { Code int Message string Err error } func (e *DBError) Error() string { return fmt.Sprintf("[%d] %s: %v", e.Code, e.Message, e.Err) } func HandleDBError(err error) error { if err == nil { return nil } if errors.Is(err, gorm.ErrRecordNotFound) { return &DBError{Code: 404, Message: "Record not found", Err: err} } if strings.Contains(err.Error(), "Duplicate entry") { return &DBError{Code: 409, Message: "Record already exists", Err: err} } if strings.Contains(err.Error(), "foreign key constraint") { return &DBError{Code: 400, Message: "Associated record does not exist", Err: err} } return &DBError{Code: 500, Message: "Database error", Err: err} } // Usage if err := db.Create(&user).Error; err != nil { return HandleDBError(err) }
3. Error Logging
go// Configure logger import "gorm.io/gorm/logger" newLogger := logger.New( log.New(os.Stdout, "\r\n", log.LstdFlags), logger.Config{ SlowThreshold: time.Second, LogLevel: logger.Error, IgnoreRecordNotFoundError: true, Colorful: true, }, ) db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ Logger: newLogger, }) // Custom error handling func logError(operation string, err error) { if err != nil { log.Printf("%s failed: %v", operation, err) // Can send to monitoring system // metrics.ErrorCounter.Inc() } } // Usage if err := db.Create(&user).Error; err != nil { logError("Create user", err) }
4. Retry Mechanism
gofunc withRetry(maxRetries int, fn func() error) error { var lastErr error for i := 0; i < maxRetries; i++ { if err := fn(); err != nil { lastErr = err // If it's a connection error, can retry if isConnectionError(err) { time.Sleep(time.Second * time.Duration(i+1)) continue } // Other errors don't retry return err } return nil } return fmt.Errorf("Still failed after %d retries: %v", maxRetries, lastErr) } func isConnectionError(err error) bool { return strings.Contains(err.Error(), "connection") || strings.Contains(err.Error(), "timeout") } // Usage err := withRetry(3, func() error { return db.Create(&user).Error })
Error Recovery
Use recover to handle panic
gofunc safeDBOperation(db *gorm.DB, operation string, fn func(*gorm.DB) error) (err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("%s panic occurred: %v", operation, r) log.Printf("Panic recovered: %v", r) } }() return fn(db) } // Usage err := safeDBOperation(db, "Create user", func(db *gorm.DB) error { return db.Create(&user).Error })
Error Handling Middleware
Create Error Handling Middleware
gotype DBHandler struct { db *gorm.DB } func (h *DBHandler) HandleError(err error) error { if err == nil { return nil } if errors.Is(err, gorm.ErrRecordNotFound) { return &AppError{ Code: http.StatusNotFound, Message: "Resource not found", Err: err, } } return &AppError{ Code: http.StatusInternalServerError, Message: "Database operation failed", Err: err, } } func (h *DBHandler) CreateUser(user *User) error { if err := h.db.Create(user).Error; err != nil { return h.HandleError(err) } return nil }
Notes
- Always check errors: Don't ignore any database operation errors
- Distinguish error types: Take different handling strategies based on different error types
- Provide meaningful error messages: Error messages should be clear and specific
- Log errors: Record detailed error information for debugging
- Avoid exposing sensitive information: Don't expose database errors directly to users
- Use transactions: For multiple operations, use transactions to ensure data consistency
- Retry mechanism: For temporary errors, implement retry mechanism
- Monitoring and alerting: Set up error monitoring and alerting mechanisms
Common Questions
Q: How to distinguish between record not found and other errors?
A: Use errors.Is(err, gorm.ErrRecordNotFound) to check if it's a record not found error.
Q: Should error information be logged or returned to the user?
A: Detailed error information should be logged, and simplified, friendly error messages should be returned to the user.
Q: How to handle database connection disconnection?
A: Implement retry mechanism, or use connection pool auto-reconnect feature.
Q: How to handle errors in transactions?
A: Returning an error in the transaction callback will automatically rollback the transaction, no manual handling needed.