You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular http.post返回200但数据库偶发未保存食材/步骤问题排查

食谱提交时食材/步骤随机无法保存问题

背景信息

  • 技术栈:前端Angular、后端Spring Boot、本地SQL数据库
  • 数据模型:Recipe与Ingredient、Recipe与Step均为多对多关系

问题现象

提交食谱时,食材和步骤仅能偶尔完整保存到数据库:

  • 重复提交同一食谱10次,仅2-3次能成功保存全部5个食材和3个步骤
  • 部分提交的网络请求全部返回200状态码,请求次数符合预期,但数据库中仍有食材/步骤缺失
  • 全程无任何报错信息

相关代码

Angular 提交逻辑(onSubmit函数)

onSubmit() {
    for (let i = 0; i < this.ingredientsForRecipe.length; i++) {
      this.recipeService.addRecipeIngredient(this.recipeId, this.ingredientsForRecipe[i]).subscribe();
    }

    for (let i = 0; i < this.stepsForRecipe.length; i++) {
      this.recipeService.addRecipeStep(this.recipeId, this.stepsForRecipe[i]).subscribe();
    }
}

Angular 服务函数

public addRecipeIngredient(recipeId: number, ingredient: Ingredient) {
    const url = 'http://localhost:8080/recipes/' + recipeId + '/addIngredients';
    return this.http.post(url, ingredient);
}

public addRecipeStep(recipeId: number, step: Step) {
    const url = 'http://localhost:8080/recipes/' + recipeId + '/addSteps';
    return this.http.post(url, step);
}

Spring 后端接口

@PostMapping("recipes/{id}/addIngredients")
public void addIngredientsToRecipe(@PathVariable("id") Integer recipeId, @RequestBody Ingredient ingredient) {
    Optional<Recipe> recipeResult = recipeRepository.findById(recipeId);
    Recipe recipe = recipeResult.get();
    RecipeIngredientDTO recipeIngredientDTO = new RecipeIngredientDTO();
    recipeIngredientDTO.setRecipe(recipe);
    recipeIngredientDTO.setIngredient(ingredient);
    recipe.addIngredient(ingredient);
    recipeRepository.save(recipe);
    ingredientRepository.save(ingredient);
}

@PostMapping("recipes/{id}/addSteps")
public void addStepsToRecipe(@PathVariable("id") Integer recipeId, @RequestBody Step step) {
    Optional<Recipe> recipeResult = recipeRepository.findById(recipeId);
    Recipe recipe = recipeResult.get();
    RecipeStepDTO recipeStepDTO = new RecipeStepDTO();
    recipeStepDTO.setRecipe(recipe);
    recipeStepDTO.setStep(step);
    recipe.addStep(step);
    recipeRepository.save(recipe);
    stepRepository.save(step);
}

排查方向与解决方案

  1. 并发更新冲突

    • 前端同时发起多个POST请求修改同一个Recipe对象,后端每次查询Recipe后修改保存,会出现丢失更新问题:多个请求读取到同一版本的Recipe,后保存的请求会覆盖前一个请求的关联关系。
    • 解决方案:
      • 前端:改用forkJoin批量发起请求,或直接让后端提供批量添加食材/步骤的接口,一次性提交所有数据,从根源避免并发更新。
      • 后端:在更新Recipe时使用乐观锁(添加@Version字段),或查询时加悲观锁(如recipeRepository.findById(recipeId, LockModeType.PESSIMISTIC_WRITE)),防止并发修改。
  2. 事务未正确管理

    • 当前后端接口无显式事务声明,添加食材/步骤的操作可能因隐性异常导致数据未持久化。
    • 解决方案:在接口方法上添加@Transactional注解,确保关联关系保存和实体保存操作在同一事务中完成。
  3. 多对多关系映射问题

    • 检查Recipe类中addIngredient和addStep方法是否维护了双向关联:添加Ingredient/Step时,需同时将当前Recipe加入对应实体的关联集合,否则Hibernate可能无法正确生成关联表数据。
    • 示例:
      public void addIngredient(Ingredient ingredient) {
          this.ingredients.add(ingredient);
          ingredient.getRecipes().add(this); // 维护双向关联
      }
      
  4. 请求错误静默丢失

    • 前端subscribe()未处理错误回调,可能存在请求静默失败的情况(如隐性超时)。
    • 解决方案:添加错误日志排查:
      this.recipeService.addRecipeIngredient(this.recipeId, ing)
        .subscribe({
          next: () => console.log('食材添加成功'),
          error: (err) => console.error('食材添加失败:', err)
        });
      

内容的提问来源于stack exchange,提问作者C T

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 06:10:49