【问题标题】:GET request and Error Handling with the JpaRepository .findById() method [duplicate]使用 JpaRepository .findById() 方法获取请求和错误处理 [重复]
【发布时间】:2021-10-12 12:07:11
【问题描述】:

所以我正在尝试学习 Spring Boot 和 Rest API 的各种资源,我在几个不同的教程和教科书中遇到了同样的问题。它似乎源于 CrudRepository 接口,更具体地说是 JpaRepository.findById() 方法。

我读过的每个教程都有以下内容:

@GetMapping("/{id}")
    public ResponseEntity<UserDTO> getUserById(@PathVariable("id") final Long id){
        UserDTO user = userJpaRepository.findById(id);
        if (user == null) {
            return new ResponseEntity<UserDTO>(
                    new CustomErrorType("User with id " + id + " not found"),
                    HttpStatus.NOT_FOUND);
        }
        return new ResponseEntity<UserDTO>(user, HttpStatus.OK);

但是, UserDTO user = userJpaRepository.findById(id); 不会编译。

我想如果我将其更改为UserDTO user = userJpaRepository.findById(id).get();,它会编译、运行并且 GET 成功。问题是如果在 GET 请求中找不到用户 ID,它不会返回 NULL,并且我会收到 500 内部服务器错误。

来自我的 IDE 的工具提示和建议将代码更正为

@GetMapping("/{id}")
    public ResponseEntity<UserDTO> getUserById(@PathVariable("id") final Long id){
        Optional<UserDTO> user = userJpaRepository.findById(id);
        if (user == null) {
            return new ResponseEntity<UserDTO>(
                    new CustomErrorType("User with id " + id + " not found"),
                    HttpStatus.NOT_FOUND);
        }
        return new ResponseEntity<UserDTO>(HttpStatus.OK);
    }

这与 GET 请求和错误处理一样有效。为什么所有教程都以第一种方式列出? 有人可以向我解释这里发生了什么吗?

【问题讨论】:

  • 您需要确定您的数据库中存在一些值为id 的记录,也许这就是教程这样做的原因。拥有 Optional 通用 imo 更安全,编写 if 条件并记录 null 比在生产中找到它要容易得多。
  • stackoverflow.com/a/49317013/2612030。显然 API 已更改。
  • @MauricioGraciaGutierrez 至于为什么它的呈现方式不同,是的。至于实际发生了什么以及我如何让它发挥作用,不。我试图弄清楚不同的 HTTP 请求以及它们如何对应 CRUD 接口。只要字段有效,HTTP 方法就可以正常工作。我已经设法让GET 请求工作并以一种可以接受的方式处理错误。但是,POST PUTDELETE http 方法的错误处理都没有按照我所关注的本书所表明的方式进行。

标签: java spring-boot rest


【解决方案1】:

JpaRepository.getById 可以通过 id 检索数据库记录。 该方法是预定义的,与findAll 方法相同。 提到的方法CrudRepository.findById 继承自祖先类CrudRepository。它返回一个Optional&lt;T&gt;因为 spring-data 迁移到 Java 8(从 Spring-Data-Jpa 2.0 版开始)。

查看更多: Spring Data JPA findOne() change to Optional how to use this?

实现 JpaRepository 的查找方法

但是,如果您实现JpaRepository 并添加一个新的findByName 方法,这将依赖于JPA's Query Language (JPQL) 并隐式发出一个准备好的语句,如SELECT * FROM table WHERE name = ?。这里WHERE子句的谓词如name = ?是从方法名中提取和构建的,在By之后。

Optional 背后的理性回归

任何find 方法的默认返回都是Optional&lt;T&gt; 自Java 8(引入了Optional 类型)。 这是因为搜索可能会找到一些,也可能找不到。如果没有找到,返回Optional 而不是null 或抛出异常会更安全。 Optional 回报背后的主要好处和意图是:

  • signal empty = 方法签名中的可选结果(而不是之前的隐式null
  • 强制 API 用户/开发者处理空返回
  • 避免偶然的NullPointerExceptions (NPE)

更新以处理发现的空结果:

在 REST 控制器中处理 NOT FOUND

在 CRUD 资源上的结果为空或未找到的情况下响应的 RESTful 方式是:

  • 返回 HTTP 状态 404 NOT FOUND 以及一些自定义/描述性错误消息。

这条“不愉快的道路”可以通过像Baeldung's REST exception-handling tutorial 中描述的那样抛出 Spring 的 ResponseStatusException 来轻松实现:

@GetMapping("/{id}")
public UserDTO getUserById(@PathVariable("id") final Long id) {
    Optional<UserDTO> user = userJpaRepository.findById(id);
    if (user.isEmpty()) {
        throw new ResponseStatusException(HttpStatus.NOT_FOUND, String.format("User with id %d not found", id));
    }

    return user;
}

简化的内容:

  • 抛出 ResponseStatusException 会导致异常返回,并向 Spring 发出信号以指定状态 (404) 和正文(消息)进行响应。
  • 控制器方法可以简单地返回 UserDTO 类型(无需包装在 ResponseEntity 中),因为 Spring 将转换为响应表示
  • 对于@GetMappingreturn 总是默认分配HttpStatus.OK

进一步,像Mauricio Gracia Gutierrez tutoring answer解释ideomatic Optional处理,方法体可以简化为单行

return userJpaRepository.findById(id)
       .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, String.format("User with id %d not found", id)));

【讨论】:

  • 这对我来说很有意义。我不知道Optional 是一种类型。根据您所说的,如果在找不到 ID 时它没有返回 null,为什么在第二个示例中执行 if 语句?
  • @JeffO。嗯,Optional 在实际意义上更像是一个类型包装器,具体来说是一个泛型类型if (user == null) 最有可能永远是 false。处理Optional 返回的常用方法是建议if (user.isEmpty())
  • @hc_dev 感谢user.isEmpty() 对我来说比user == null 更有意义,但是如果 GET 请求是针对错误的 ID,它们都可以工作。但是,我在文档中看到不应使用 Optional ==
  • @JeffO。我在水平标尺之后更新了我的答案,以建议改进 Spring REST 控制器中的 NOT FOUND 处理?️看看如何结合Mauricio's answer 的实用提示简化代码。
  • 非常有帮助,谢谢。一周以来,我一直在尝试通过几种不同的资源开始使用 REST api。他们都以我的第一个代码块的格式化方式呈现了错误处理。非常令人沮丧。
【解决方案2】:

关于如何使用OptionalOptional&lt;T&gt; findById(ID id) 的一些提示。

通常,当您通过 id 查找实体时,如果没有检索到,您希望返回它或进行特定处理。

以下是三个经典用法示例。

  1. 假设如果找到实体,您想要获取它,否则您想要获取默认值。

你可以写:

Foo foo = repository.findById(id)
                    .orElse(new Foo());

或在有意义的情况下获取 null 默认值(与 API 更改之前的行为相同):

Foo foo = repository.findById(id)
                    .orElse(null);
  1. 假设如果实体被发现你想返回它,否则你想抛出一个异常。

你可以写:

return repository.findById(id)
        .orElseThrow(() -> new EntityNotFoundException(id));
  1. 假设您想根据是否找到实体应用不同的处理(不一定抛出异常)。

你可以写:

Optional<Foo> fooOptional = fooRepository.findById(id);
if (fooOptional.isPresent()) {
    Foo foo = fooOptional.get();
    // processing with foo ...
} else {
    // alternative processing....
}

摘自 Spring Data JPA findOne() change to Optional how to use this?

【讨论】:

  • 这些关于条件行为(分支执行)的可选用法的提示是有用的 Java 模式 - 不仅适用于与 JPA 相关的查找结果。
猜你喜欢
  • 2016-05-29
  • 1970-01-01
  • 2017-05-20
  • 1970-01-01
  • 1970-01-01
  • 2012-10-05
  • 1970-01-01
  • 2019-07-23
  • 2018-03-15
相关资源
最近更新 更多