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

GORM 中如何处理错误?

3月6日 21:37

GORM 提供了完善的错误处理机制,正确处理错误对于构建稳定的应用程序至关重要。

错误处理基础

检查错误

GORM 的所有操作都会返回错误,需要检查 db.Error

go
// 创建记录 if err := db.Create(&user).Error; err != nil { log.Printf("创建用户失败: %v", err) return err } // 查询记录 if err := db.First(&user, 1).Error; err != nil { log.Printf("查询用户失败: %v", err) return err } // 更新记录 if err := db.Model(&user).Update("name", "John").Error; err != nil { log.Printf("更新用户失败: %v", err) return err } // 删除记录 if err := db.Delete(&user).Error; err != nil { log.Printf("删除用户失败: %v", err) return err }

常见错误类型

1. 记录未找到错误

go
var user User result := db.First(&user, 999) if errors.Is(result.Error, gorm.ErrRecordNotFound) { log.Println("用户不存在") // 处理记录未找到的情况 } else if result.Error != nil { log.Printf("查询失败: %v", result.Error) return result.Error }

2. 连接错误

go
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { log.Printf("数据库连接失败: %v", err) panic(err) } // 测试连接 sqlDB, err := db.DB() if err != nil { log.Printf("获取数据库连接失败: %v", err) panic(err) } if err := sqlDB.Ping(); err != nil { log.Printf("数据库 ping 失败: %v", err) panic(err) }

3. 约束错误

go
// 唯一约束冲突 user := User{Email: "existing@example.com"} if err := db.Create(&user).Error; err != nil { if strings.Contains(err.Error(), "Duplicate entry") { log.Println("邮箱已存在") return errors.New("邮箱已存在") } return err } // 外键约束错误 if err := db.Create(&order).Error; err != nil { if strings.Contains(err.Error(), "foreign key constraint") { log.Println("用户不存在") return errors.New("用户不存在") } return err }

4. 验证错误

go
// 使用钩子进行验证 func (u *User) BeforeCreate(tx *gorm.DB) error { if u.Name == "" { return errors.New("用户名不能为空") } if !strings.Contains(u.Email, "@") { return errors.New("邮箱格式不正确") } return nil } // 创建时处理验证错误 user := User{Name: "", Email: "invalid"} if err := db.Create(&user).Error; err != nil { log.Printf("验证失败: %v", err) return err }

错误处理最佳实践

1. 使用事务处理错误

go
err := db.Transaction(func(tx *gorm.DB) error { if err := tx.Create(&user).Error; err != nil { return err // 自动回滚 } if err := tx.Create(&profile).Error; err != nil { return err // 自动回滚 } return nil // 自动提交 }) if err != nil { log.Printf("事务失败: %v", err) return err }

2. 自定义错误处理

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: "记录未找到", Err: err} } if strings.Contains(err.Error(), "Duplicate entry") { return &DBError{Code: 409, Message: "记录已存在", Err: err} } if strings.Contains(err.Error(), "foreign key constraint") { return &DBError{Code: 400, Message: "关联记录不存在", Err: err} } return &DBError{Code: 500, Message: "数据库错误", Err: err} } // 使用 if err := db.Create(&user).Error; err != nil { return HandleDBError(err) }

3. 错误日志记录

go
// 配置日志 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, }) // 自定义错误处理 func logError(operation string, err error) { if err != nil { log.Printf("%s 失败: %v", operation, err) // 可以发送到监控系统 // metrics.ErrorCounter.Inc() } } // 使用 if err := db.Create(&user).Error; err != nil { logError("创建用户", err) }

4. 重试机制

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 isConnectionError(err) { time.Sleep(time.Second * time.Duration(i+1)) continue } // 其他错误不重试 return err } return nil } return fmt.Errorf("重试 %d 次后仍然失败: %v", maxRetries, lastErr) } func isConnectionError(err error) bool { return strings.Contains(err.Error(), "connection") || strings.Contains(err.Error(), "timeout") } // 使用 err := withRetry(3, func() error { return db.Create(&user).Error })

错误恢复

使用 recover 处理 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: %v", operation, r) log.Printf("Panic recovered: %v", r) } }() return fn(db) } // 使用 err := safeDBOperation(db, "创建用户", func(db *gorm.DB) error { return db.Create(&user).Error })

错误处理中间件

创建错误处理中间件

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: "资源不存在", Err: err, } } return &AppError{ Code: http.StatusInternalServerError, Message: "数据库操作失败", Err: err, } } func (h *DBHandler) CreateUser(user *User) error { if err := h.db.Create(user).Error; err != nil { return h.HandleError(err) } return nil }

注意事项

  1. 始终检查错误:不要忽略任何数据库操作的错误
  2. 区分错误类型:根据不同的错误类型采取不同的处理策略
  3. 提供有意义的错误信息:错误信息应该清晰、具体
  4. 记录错误日志:记录详细的错误信息便于调试
  5. 避免暴露敏感信息:不要将数据库错误直接暴露给用户
  6. 使用事务:对于多个操作,使用事务确保数据一致性
  7. 重试机制:对于临时性错误,可以实现重试机制
  8. 监控告警:设置错误监控和告警机制

常见问题

Q: 如何区分记录未找到和其他错误?

A: 使用 errors.Is(err, gorm.ErrRecordNotFound) 来判断是否是记录未找到错误。

Q: 错误信息应该记录到日志还是返回给用户?

A: 详细错误信息应该记录到日志,返回给用户的应该是简化的、友好的错误信息。

Q: 如何处理数据库连接断开的情况?

A: 实现重试机制,或者使用连接池自动重连功能。

Q: 事务中的错误如何处理?

A: 在事务回调中返回错误会自动回滚事务,不需要手动处理。

标签:Gorm