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); }
排查方向与解决方案
并发更新冲突
- 前端同时发起多个POST请求修改同一个Recipe对象,后端每次查询Recipe后修改保存,会出现丢失更新问题:多个请求读取到同一版本的Recipe,后保存的请求会覆盖前一个请求的关联关系。
- 解决方案:
- 前端:改用
forkJoin批量发起请求,或直接让后端提供批量添加食材/步骤的接口,一次性提交所有数据,从根源避免并发更新。 - 后端:在更新Recipe时使用乐观锁(添加
@Version字段),或查询时加悲观锁(如recipeRepository.findById(recipeId, LockModeType.PESSIMISTIC_WRITE)),防止并发修改。
- 前端:改用
事务未正确管理
- 当前后端接口无显式事务声明,添加食材/步骤的操作可能因隐性异常导致数据未持久化。
- 解决方案:在接口方法上添加
@Transactional注解,确保关联关系保存和实体保存操作在同一事务中完成。
多对多关系映射问题
- 检查Recipe类中
addIngredient和addStep方法是否维护了双向关联:添加Ingredient/Step时,需同时将当前Recipe加入对应实体的关联集合,否则Hibernate可能无法正确生成关联表数据。 - 示例:
public void addIngredient(Ingredient ingredient) { this.ingredients.add(ingredient); ingredient.getRecipes().add(this); // 维护双向关联 }
- 检查Recipe类中
请求错误静默丢失
- 前端
subscribe()未处理错误回调,可能存在请求静默失败的情况(如隐性超时)。 - 解决方案:添加错误日志排查:
this.recipeService.addRecipeIngredient(this.recipeId, ing) .subscribe({ next: () => console.log('食材添加成功'), error: (err) => console.error('食材添加失败:', err) });
- 前端
内容的提问来源于stack exchange,提问作者C T
相关产品推荐
相关产品推荐

