Spring Data JPA的findOne()方法改为Optional后,如何使用?

101

我正在学习使用 Java8 开发 SpringBoot2.0 应用程序。

我参照了一些博客教程的示例。

该教程的源代码如下:

@GetMapping("/{id}/edit")
public String edit(@PathVariable Long id, Model model) {
  model.addAttribute("categoryDto", categoryService.findOne(id));
  return "category/edit";
}

但是这段代码会抛出错误:

categoryService.findOne(id)

我在考虑将JPA的findOne()方法更改为Optional< S >

如何解决这个问题?

更多信息:

这是categoryService方法:

public Category findOne(Long id) {
  return categoryRepository.findOne(id);
}

1
  1. 你能否放上你正在关注的博客链接或者展示完整的代码。categoryService是如何声明的?
  2. 你遇到了什么异常?
- pvpkiran
7个回答

256
从至少2.0版本开始,Spring-Data-Jpa修改了findOne()方法。
现在,findOne()既没有相同的签名也没有相同的行为。
以前,在CrudRepository接口中定义为:
T findOne(ID primaryKey);

现在,在CrudRepository中你会发现的单个findOne()方法是在QueryByExampleExecutor接口中定义的:
<S extends T> Optional<S> findOne(Example<S> example);

这最终由SimpleJpaRepository实现,是CrudRepository接口的默认实现。
此方法是一个示例查询,您不希望将其作为替代。
实际上,具有相同行为的方法仍然存在于新API中,但方法名称已更改。
CrudRepository接口中,它从findOne()重命名为findById()
Optional<T> findById(ID id); 

现在它返回一个Optional,这不错,可以防止NullPointerException
所以,现在要调用的实际方法是Optional<T> findById(ID id)
如何使用它?学习Optional的用法。以下是其规范的重要信息:
一个容器对象,可能包含或不包含非空值。如果存在值,则isPresent()将返回true并且get()将返回该值。
提供了依赖于包含值的存在或不存在的其他方法,例如orElse()(如果不存在值,则返回默认值)和ifPresent()(如果存在值,则执行代码块)。
一些关于如何使用 Optional<T> findById(ID id) 的提示。
通常,当您按id查找实体时,您希望返回它或者在未检索到时进行特定处理。
以下是三个经典的用法示例。
1. 假设如果找到实体,您想要获取它,否则您想要获取默认值。
您可以编写:
Foo foo = repository.findById(id)
                    .orElse(new Foo());

或者如果有意义的话,获取null默认值(与API更改之前相同的行为):

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

假设你想要找到实体并返回,否则抛出异常。你可以这样写:
return repository.findById(id)
        .orElseThrow(() -> new EntityNotFoundException(id));

假设您想根据实体是否被找到应用不同的处理方式(而无需抛出异常)。
您可以编写:
Optional<Foo> fooOptional = fooRepository.findById(id);
if (fooOptional.isPresent()) {
    Foo foo = fooOptional.get();
    // processing with foo ...
} else {
    // alternative processing....
}

5
第一部分是错误的:该方法并没有被移动到QueryByExampleExecutor,它只是被重命名为 findById(…) 并返回一个Optional。此外,始终在 Optional 上使用 .map(…),而不要调用 get() - Oliver Drotbohm
1
谢谢 Davidxxx。你救了我。 - Priyantha
@davidxxx,如果我不想使用findById(...),而是想通过其他字段/条件(例如名称或其他内容)进行查找呢? - Artanis Zeratul
2
也许使用EntityNotFoundException比NotFoundEntity更好。 - zhouji
1
@zhouji 同意并更新。此外,它在 javax.persistence API 中被定义为 RuntimeException。因此非常合理。 - davidxxx
显示剩余7条评论

13

这个方法已经被重命名为findById(…),返回一个Optional,所以您需要自己处理不存在的情况:

Optional<Foo> result = repository.findById(…);

result.ifPresent(it -> …); // do something with the value if present
result.map(it -> …); // map the value if present
Foo foo = result.orElse(null); // if you want to continue just like before

4
如何思考 return repository.findById(id).orElseThrow(() -> new NotFoundEntity(id)):返回 repository 中 id 对应的实体,如果该实体不存在则抛出 NotFoundEntity 异常。 - Hulk Choi
这个人说这是错误的:https://tuhrig.de/anti-pattern-dont-use-optionals-for-data-repositories/ - Yan Burtovoy
显然,有人在互联网上是错误的 - 或者至少不够细致。¯\(ツ) - Oliver Drotbohm
我认为他是正确的。对于findById,您具有来自数据存储的特定标识符。在这种情况下返回可选项没有意义。 - IcedDante

6

事实上,在最新版的Spring Data中,findOne方法返回一个Optional类型。如果你想从Optional中检索出对象,可以直接使用Optional的get()方法。但是首先,一个仓库(repository)应该将Optional类型返回给服务(service),然后服务再处理Optional为空的情况。之后,服务应该将对象返回给控制器(controller)。


2
建议在 Optional 上调用 ….get() 是错误的建议。Optional 有专门的方法来处理值。 - Oliver Drotbohm
2
实际上,为了处理值,有更好的方法,比如 .orElse() 或 .orElseThrow()。但是如果只是简单地检索项目而不检查存储库调用是否返回所需对象,则 .get() 应该就可以了。 - Claudiu Guja
@OliverDrotbohm 当所有你能做的就是抛出运行时异常并中止代码时,可选项就没有意义了。在这些情况下,我看到开发人员通过使用flatMap并忽略对象已经消失的情况而无意中吞噬了错误/异常/错误。如果代码不能在没有该对象的情况下继续进行,则get非常合理,其他方法将掩盖错误。这与已检查异常与未检查异常没有区别。如果没有什么可以做的,那么运行时/空指针就可以了,所以调用.get()。如果有其他操作用户可以执行,orElse就有意义了。 - Usman Mutawakil

4
我常常在广泛使用的CrudRepository repos/interfaces中编写一个默认方法“findByIdOrError”。
@Repository 
public interface RequestRepository extends CrudRepository<Request, Integer> {

    default Request findByIdOrError(Integer id) {
        return findById(id).orElseThrow(EntityNotFoundException::new);
    } 
}

但是这将返回一个请求对象。我应该如何将其转换为我正在搜索的实体(如果存在)? - sujumayas

2

Optional API提供了获取值的方法。您可以检查isPresent()以确定该值是否存在,然后调用get(),或者您可以将get()orElse()链接,并提供默认值。

最后一件可以尝试的事情是在自定义方法上使用@Query()


3
建议调用 ....get() 方法来获取 Optional 对象的值是错误的。Optional 类有专门的方法来处理该值。 - Oliver Drotbohm

2
自Spring Data Commons 2.0版本以来,CrudRepository接口findOne方法已被替换为findById。将findOne(id) 替换为:
findById(id).orElse(null)

0
考虑一个User实体和UserRepository。在服务包代码中,如下所示。
Optional<User> resultUser = UserRepository.findById(userId);  //return Optional

User createdUser = resultUser.get();  //return User

现在你可以使用getter访问所有用户实体属性。

createdUser.getId(); 
createdUser.getName();

就像这样。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接