跨包传递context.Context存取值:数据库事务传递失败排查
问题原因及解决方法
核心问题
context.WithValue 不会修改原有Context,而是返回一个包含新值的全新Context实例。你在service层调用该方法后,既没有将新Context返回给上层,也没有用它执行后续操作,导致server层的原始Context完全没带上事务数据,自然拿不到值。
修复步骤
1. 定义Context的自定义Key(避免冲突)
不要直接用字符串作为key,建议在项目里定义专属的key类型:
// 可放在公共包(比如contexts包)中 package contexts type ctxKey string const TransactionKey ctxKey = "transaction"
2. 修改Service层,返回新Context并使用事务执行操作
service层需要返回新的Context,并且要用开启的事务执行数据库操作,否则事务不会生效:
package services import ( "context" "e-commerce/models" "e-commerce/contexts" // 引入自定义key的包 ) func (s Service) CreateCategory(ctx context.Context, category *models.Category) (context.Context, *models.Category, error) { err := category.Validate() if err != nil { return ctx, nil, err } tx := s.userRepository.Db().Begin() // 创建携带事务的新Context newCtx := context.WithValue(ctx, contexts.TransactionKey, tx) // 用事务tx执行Create,而非原Db()实例 result := tx.Create(&category) if result.Error != nil { tx.Rollback() // 出错先回滚 return newCtx, nil, result.Error } return newCtx, category, nil }
3. 修改Server层,接收新Context并传递给响应方法
在server的CreateCategory方法中,接收service返回的新Context,再传给returnResponse:
package server import ( "e-commerce/models" "e-commerce/contexts" "encoding/json" "net/http" ) func (s *Server) CreateCategory(w http.ResponseWriter, req *http.Request) { ctx := req.Context() var category models.Category err := json.NewDecoder(req.Body).Decode(&category) if err != nil { returnResponse(ctx, w, http.StatusBadRequest, err) return } // 接收service返回的新Context newCtx, _, err := s.service.CreateCategory(ctx, &category) if err != nil { returnResponse(newCtx, w, http.StatusBadRequest, err) return } returnResponse(newCtx, w, http.StatusOK, err) }
4. 完善响应方法的空值判断
避免nil panic,先判断事务是否存在:
package server import ( "context" "e-commerce/config" "e-commerce/services" "e-commerce/contexts" "github.com/gorilla/mux" "gorm.io/gorm" "net/http" ) func returnResponse(ctx context.Context, w http.ResponseWriter, status int, err error) { tx, ok := ctx.Value(contexts.TransactionKey).(*gorm.DB) if ok { if status != http.StatusOK { tx.Rollback() } else { tx.Commit() } } w.WriteHeader(status) if err != nil { w.Write([]byte(err.Error())) } }
额外提示
- 事务的生命周期建议由上层(如server层)管理,service层专注业务逻辑,职责划分更清晰。
- 若多个service方法需要共享同一事务,务必确保传递的是携带事务的Context。
内容的提问来源于stack exchange,提问作者David
相关产品推荐
相关产品推荐

