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.