首页 > 解决方案 > 在 SpringBoot 中管理并发 POST 请求最佳实践

问题描述

我正在使用 SpringBoot 构建一个 REST-API 并使用这个控制器。

@RestController
class EmployeeController {

  private final EmployeeRepository repository;

  EmployeeController(EmployeeRepository repository) {
    this.repository = repository;
  }

  @GetMapping("/employees")
  List<Employee> all() {
    return repository.findAll();
  }

  @PostMapping("/employees")
  Employee newEmployee(@RequestBody Employee newEmployee) {
    return repository.save(newEmployee);
  }

我想确保 API-Consumers 不能使用相同的Employee. 我知道我可以在保存之前检查实体是否已经存在于数据库中,但我担心性能会很差。我也已经注意到你可以像@version在你的实体中一样使用 Annotation 来更新现有实体的更多保存。

但是在 Spring 中是否还有一种方法或最佳实践如何处理这个带有潜在新实体的 POST 请求?

标签: javaspringrestposthttp-post

解决方案


您期望POST /employees端点的请求吞吐量是多少?虽然性能很重要,但过早的优化几乎总是会导致你的代码比它需要的更难看,而且几乎没有什么收获。

正如您的代码当前所代表的那样,多个并发POST /employees请求最终将以先到先服务的方式结束,在该基础上创建具有UNIQUE应用程序中给定约束的第一个用户(希望由您的底层 DBMS 强制执行),以及之后的所有其他请求(对于同一用户)将由于 eg ConstraintViolationException(映射到 eg DataIntegrityViolationException)而失败。从这个角度来看(只要你没有复杂的分布式DBMS设置),数据的一致性还是有保证的。

当然,缺点是返回的错误消息是:

  1. 供应商特定并泄漏底层实现(例如,我们向客户端显示我们正在使用 Hibernate)
  2. 客户端可能难以解析。

如果您改为将实现更改为如下所示:

  @PostMapping("/employees")
  Employee newEmployee(@RequestBody Employee newEmployee) {
    verifyEmployeeDoesNotExist(newEmployee);
    return repository.save(newEmployee);
  }

  private void verifyUserDoesNotExist(Employee employee) {
    if (repository.exists(newEmployee) {
      throw new EmployeeAlreadyExistsException("Employee " + newEmployee.getName() + " already exists";
    }
  }

那么您可以更轻松地控制端点和底层流程的控制流,这可能会允许更容易消化的异常处理。这可以通过添加例如自定义异常来进一步改进,这些异常还包含一些预定义的代码,error code例如error 409 code 1010 Employee already exists

当然,Spring 内置的 Hibernate 异常转换(例如HibernateExceptionTranslator)对于您的用例可能已经足够好,甚至可以扩展,甚至可以推广此扩展。

最后,最佳实践是使您的代码干净、可读和可维护。然后开始添加功能来监控您的代码。在那之后,只有你遇到性能问题时,你仍然可以优化它。


推荐阅读