29

I'm working on a weird issue, I was doing integration testing, calling my controller to get an object from database that doesn't exist.

public Optional<T> get(Long id) {

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

When getOne(…) is not able to find anything, I was expecting an EntityNotFoundException but actually nothing. If I inspect my result I can see that I have an empty entity with a handler link to it "threw EntityNotFoundException" but we don't go in the catch and I return an optional of this weird entity.

I can't understand this behavior.

Oliver Drotbohm
  • 80,157
  • 18
  • 225
  • 211
Seb
  • 3,602
  • 8
  • 36
  • 52

2 Answers2

38

This is due to the way JPA specifies EntityManager.getReference(…) to work. It's supposed to return a proxy that will either resolve the object to be returned on the first access of a property or throw the contained exception eventually.

The easiest way to work around this is to simply use findOne(…) instead, like this Optional.ofNullable(repository.findOne(…)). findOne(…) will return nullin case no result is found.

A more advanced way of resolving this is to make the repository return Optional instances directly. This can be achieved by creating a custom base repository interface using Optional<T> as return type for the find…-methods.

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> { … }

Find a complete example of this in the Spring Data examples repository.

Oliver Drotbohm
  • 80,157
  • 18
  • 225
  • 211
  • 1
    Looks like a good idea ! I'll give it a try rigth away But ... what about the performance, is findOne as good as getOne ? – Seb Sep 01 '15 at 18:45
  • 5
    There indeed is a difference as `getOne(…)` doesn't immediately materialize the object while `findOne(…)` does. The downside of the former is that it delays the failure in the lookup, too, which bites you here. Also, `findOne(…)` will hit the first level cache in the `EntityManager` so within a particular session, you won't hit the database twice. If this really becomes an issue you might wanna plug a cache explicitly but I'd delay that until measurement really shows, there's a problem. – Oliver Drotbohm Sep 01 '15 at 18:50
  • 1
    it's worth noting that as of 2019, `CrudRepository` already brings `Optional findById(ID id);` which does pretty much what this answer suggested in 2015... – Clint Eastwood Jul 31 '19 at 14:45
6

In Spring @Repository classes, the getOne(id) method doesn't always verify existence until the object is queried (by calling entity.getId() or something) so it's no-such-entity exception may be delayed. To validate right away, use findById(id) instead (which returns an Optional<EntityType> which will be empty if the entity with that id doesn't exist).


This is how it worked for me

public User findUserById(Long id) {
    return userRepository.findById(id).orElse(null);
}
Community
  • 1
  • 1
VK321
  • 5,768
  • 3
  • 44
  • 47