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

How to handle errors in GORM?

3月6日 21:37

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

go
var 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

go
db, 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

go
err := 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

go
type 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

go
func 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

go
func 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

go
type 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

  1. Always check errors: Don't ignore any database operation errors
  2. Distinguish error types: Take different handling strategies based on different error types
  3. Provide meaningful error messages: Error messages should be clear and specific
  4. Log errors: Record detailed error information for debugging
  5. Avoid exposing sensitive information: Don't expose database errors directly to users
  6. Use transactions: For multiple operations, use transactions to ensure data consistency
  7. Retry mechanism: For temporary errors, implement retry mechanism
  8. 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.

标签:Gorm