【问题标题】:Spring Data JPA - how to implement proper exception handling?Spring Data JPA - 如何实现正确的异常处理?
【发布时间】:2023-02-10 10:18:06
【问题描述】:

我正在使用 Spring Data JPA 来处理数据库调用。为此,我创建了:

  1. 一个 EmployeeRepository 接口,它扩展了 JpaRepository<Employee, Long>

  2. 一个 EmployeeService,它定义了三个方法:

     Employee saveEmployee(Employee employee);
     Optional<Employee> getEmployee(Long id);
     Long deleteEmployee(Long id);
    
  3. EmployeeService 的实现:

     @Override
     public Employee saveEmployee(Employee employee) {
         return employeeRepository.save(employee);
     }
    
     @Override
     public Optional<Employee> getEmployee(Long id) {
         return employeeRepository.findEmployeeById(id);
     }
    
     @Override
     public Long deleteEmployee(Long id) {
         employeeRepository.deleteById(id);
         return id;
     } 
    

    问题如下:

    get-methods 工作正常,可以返回一个可选的。另一方面,保存方法不能返回一个可选的。显然 JpaRepository 在调用 save() 时返回已保存对象的实例。我宁愿返回一个可选的,因为在保存员工时可能会出错,在这种情况下,我想抛出一个错误——即,只要可选不存在,我就会抛出一个错误。

    删除操作也是如此:例如,如果我要求删除一个员工并传入一个不存在的 id 怎么办?如果删除操作成功,我想捕获此错误,然后才返回传入的 ID。为此我必须捕获哪个错误?谁可以给我解释一下这个?

    =================================================

    更新:

    • 我通过简单地在调用 deleteById(id) 之前检查给定的 employee-id 是否存在来解决 delete-call 的问题;如果没有,则服务返回 null,如果有,则返回 id。控制器看起来像这样:

        @DeleteMapping("/{id}")
        public ResponseEntity<Long> deleteEmployee(@PathVariable Long id) {
            Long deletedEmployeeId = employeeService.deleteEmployee(id);
            if (deletedEmployeeId != null) {
                return ResponseEntity.ok(deletedEmployeeId);
        } else {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST);
        }
      

    但是,我缺少 DataAccessException。那么,我是否真的必须执行以下操作:

        @DeleteMapping("/{id}")
        public ResponseEntity<Long> deleteEmployee(@PathVariable Long id) {
            try {
                Long deletedEmployeeId = employeeService.deleteEmployee(id);
                if (deletedEmployeeId != null) {
                    return ResponseEntity.ok(deletedEmployeeId);
                } else {
                    return ResponseEntity.status(HttpStatus.BAD_REQUEST);
                }
             } catch (DataAccessException e) {
                 return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
             }
    

    老实说,这看起来有点矫枉过正。

    • 我仍然不确定如何处理保存调用。在我发布这个问题之前,我的控制器只是在做以下事情:

        @PostMapping
        public ResponseEntity<Employee> saveEmployee(@RequestBody Employee employee) {
            return ResponseEntity.ok(employeeService.saveEmployee(employee));
        }
      

    如果 employeeService.saveEmployee(employee) 抛出 DataAccessException 会发生什么?当我将响应包装在 ResponseEntity.ok() 中时,我是否仍会返回 HTTP-status-code 200?

    如果是这样,我建议执行以下操作:

        @PostMapping
        public ResponseEntity<Employee> saveEmployee(@RequestBody Employee employee) {
            try {
                Employee savedEmployee = employeeService.saveEmployee(employee);
                return ResponseEntity.ok(savedEmployee);
            } catch (DataAccessException e) {
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR);
            }
        }
    

    这是人做的事吗?还是 DataAccessExceptions 通常被忽略,因为它们不是预期的?

【问题讨论】:

  • 当谈到 save() Optional 意味着一个对象可能存在并且不关注数据库约束。对于 delete() JPA 返回 void
  • 你试过了吗,@Override public Optional<Employee> saveEmployee(Employee employee) { return Optional.ofNullable(employeeRepository.save(employee)); }
  • 对于最后一个问题,如果这是人们所做的事情:我不会为此烦恼。当您的实体无法持久化时,将抛出错误。在你的最后一个例子中,你捕获了异常并产生了一个响应状态 500,但是当你的代码没有处理异常时,Spring 无论如何都会自己做。在某些情况下,您确实想要处理异常,对于简单的 REST 控制器,我看不到任何好处。 (除非您出于任何原因想要生成“可读”的错误消息)。
  • @SimonOelerich 感谢您的评论!那么,您是否建议像最初那样使用倒数第二个示例?在这种情况下,即使发生 DataAccessException(由于 ResponseEntity.ok()),我不是每次都返回 200 的 http 状态吗?
  • 是的,只需返回ResponseEntity.ok(emplyeeRepo.findById(id)) 就足够了。对于所有保存/查找/删除都很好。除了存储库之外,您很可能不需要该服务。您可以直接调用 JpaRepository 方法,因为异常处理由独立于其来源的 spring 完成。只有在除了简单的 bean 验证之外还有其他业务逻辑时,您才需要它。

标签: java spring-boot hibernate spring-data-jpa


【解决方案1】:

“保存”方法总是返回您要保存的相同对象。只有通过检查“id”,你才能看到对象是否被保存。但是,如果数据库中发生错误,则会抛出异常,您可以通过将“employeeRepository.save(employee)”放入 try-catch 块中来捕获它。与 deleteById 相同的方式

【讨论】:

  • 这是我必须抓住的DataAccessException吗?你会推荐这样做吗?
  • 我总是简单地使用“catch (exception e)”。但我还读到所有 JPA 异常都将转换为 DataAccessException。您可以在第一个捕获“catch(异常 e1)”中捕获 DataAccessException,如果抛出任何其他内容,则在第二个捕获“catch(异常 e2)”中捕获作为容差。
  • 好吧,非常感谢,再一次!我已经更新了我的问题,请您看一下,让我知道这是否是您的想法?
  • 考虑到如果在存储库类中的 delete 方法期间出现任何问题都会发生异常,您永远不会在控制器的 deleteEmployee 中输入“else”。我建议您将服务层中的部分重构为以下代码,然后删除控制器中的 try-catch 块:
  • public Long deleteEmployeeIU(Long id) { try { userRepository.deleteById(id);返回ID; } catch (DataAccessException e) { return Long.valueOf(0); } }
猜你喜欢
  • 1970-01-01
  • 2011-10-08
  • 2019-12-31
  • 2017-12-20
  • 1970-01-01
  • 2017-04-14
  • 1970-01-01
  • 2022-01-12
  • 2021-08-10
相关资源
最近更新 更多