为什么在Spring Data repository中调用getOne(...)方法不会抛出EntityNotFoundException异常?

29

我正在解决一个奇怪的问题,进行集成测试时调用我的控制器从数据库获取一个不存在的对象。

public Optional<T> get(Long id) {

  try {
    return Optional.ofNullable(repository.getOne(id));
  } catch(EntityNotFoundException e) {
    return Optional.empty();
  }
}

getOne(…) 无法找到任何内容时,我期望会出现一个 EntityNotFoundException,但实际上什么也没有发生。如果我检查结果,我可以看到我有一个空的实体,并且它有一个处理程序链接 "threw EntityNotFoundException",但我们没有进入 catch 块,并且我返回了这个奇怪实体的可选项。

我无法理解这种行为。


为什么你期望一个异常?Javadoc没有提到异常。 - Jens
当您在不存在的数据上调用 getOne() 时,这就是经典的 JPA 行为。 - Seb
此外,我可以看到这个异常被抛出了,但是不知何故被Spring Data处理了? - Seb
2个回答

38
这是由于JPA指定 EntityManager.getReference(…) 的工作方式所致。它应该返回一个代理,该代理将在第一次访问属性时解析要返回的对象,或者最终抛出包含的异常。
解决此问题最简单的方法是改用findOne(…),像这样:Optional.ofNullable(repository.findOne(…))。如果找不到结果,则findOne(…)将返回null
更高级的解决方法是使存储库直接返回Optional实例。这可以通过创建一个使用Optional<T>作为find…方法返回类型的自定义基本存储库接口来实现。
interface BaseRepository<T, ID extends Serializable> extends Repository<T, ID> {

  Optional<T> findOne(ID id);

  // declare additional methods if needed
}

interface YourRepository extends BaseRepository<DomainClass, Long> { … }

Spring Data示例存储库中可以找到一个完整的例子。


1
看起来是个好主意!我会立刻尝试一下。但是...关于性能,findOne和getOne一样好吗? - Seb
5
getOne(...)findOne(...)确实有区别。前者不会立即生成对象,而后者会。前者的缺点在于它延迟了查找失败的时间,这也是导致问题的原因。而且,findOne(...)将会命中EntityManager的第一级缓存,所以在特定的会话中,你不需要两次访问数据库。如果这真的成为一个问题,你可能需要显式地插入一个缓存,但我建议在测量结果真正表明存在问题之前不要这样做。 - Oliver Drotbohm
1
值得注意的是,从2019年开始,CrudRepository已经引入了Optional<T> findById(ID id);,这基本上就实现了2015年这个答案所建议的功能... - Clint Eastwood

6
在Spring的@Repository类中,getOne(id)方法不总是在查询对象(通过调用entity.getId()或其他方式)之前验证其存在性,因此它的无此实体异常可能会被延迟。为了立即验证,请改用findById(id)(它返回一个Optional<EntityType>,如果不存在具有该ID的实体,则为空)。以下是我验证过的方法:
public User findUserById(Long id) {
    return userRepository.findById(id).orElse(null);
}

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