GORM 中如何处理错误?
GORM 提供了完善的错误处理机制,正确处理错误对于构建稳定的应用程序至关重要。错误处理基础检查错误GORM 的所有操作都会返回错误,需要检查 db.Error:// 创建记录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. 记录未找到错误var user Userresult := 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. 连接错误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. 约束错误// 唯一约束冲突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. 验证错误// 使用钩子进行验证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. 使用事务处理错误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. 自定义错误处理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. 错误日志记录// 配置日志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. 重试机制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 处理 panicfunc 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})错误处理中间件创建错误处理中间件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}注意事项始终检查错误:不要忽略任何数据库操作的错误区分错误类型:根据不同的错误类型采取不同的处理策略提供有意义的错误信息:错误信息应该清晰、具体记录错误日志:记录详细的错误信息便于调试避免暴露敏感信息:不要将数据库错误直接暴露给用户使用事务:对于多个操作,使用事务确保数据一致性重试机制:对于临时性错误,可以实现重试机制监控告警:设置错误监控和告警机制常见问题Q: 如何区分记录未找到和其他错误?A: 使用 errors.Is(err, gorm.ErrRecordNotFound) 来判断是否是记录未找到错误。Q: 错误信息应该记录到日志还是返回给用户?A: 详细错误信息应该记录到日志,返回给用户的应该是简化的、友好的错误信息。Q: 如何处理数据库连接断开的情况?A: 实现重试机制,或者使用连接池自动重连功能。Q: 事务中的错误如何处理?A: 在事务回调中返回错误会自动回滚事务,不需要手动处理。