101

I'm learning SpringBoot2.0 with Java8.

And I followed some blog-making tutorial example.

The tutorial source code is:

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

But this code is throwing this error:

categoryService.findOne(id)

I'm thinking about changing the JPA findOne() method to Optional< S >

How to solve that?

More info:

This is the categoryService method:

public Category findOne(Long id) {
  return categoryRepository.findOne(id);
}
Hulk Choi
  • 1,067
  • 2
  • 8
  • 12
  • 1
    1. can you put the link to the blog u r following or can you show the full code. How is categoryService declaredß 2. What is the exception you are getting – pvpkiran Mar 16 '18 at 09:12

7 Answers7

255

From at least, the 2.0 version, Spring-Data-Jpa modified findOne().
Now, findOne() has neither the same signature nor the same behavior.
Previously, it was defined in the CrudRepository interface as:

T findOne(ID primaryKey);

Now, the single findOne() method that you will find in CrudRepository is the one defined in the QueryByExampleExecutor interface as:

<S extends T> Optional<S> findOne(Example<S> example);

That is implemented finally by SimpleJpaRepository, the default implementation of the CrudRepository interface.
This method is a query by example search and you don't want that as a replacement.

In fact, the method with the same behavior is still there in the new API, but the method name has changed.
It was renamed from findOne() to findById() in the CrudRepository interface :

Optional<T> findById(ID id); 

Now it returns an Optional, which is not so bad to prevent NullPointerException.

So, the actual method to invoke is now Optional<T> findById(ID id).

How to use that?
Learning Optional usage. Here's important information about its specification:

A container object which may or may not contain a non-null value. If a value is present, isPresent() will return true and get() will return the value.

Additional methods that depend on the presence or absence of a contained value are provided, such as orElse() (return a default value if value not present) and ifPresent() (execute a block of code if the value is present).


Some hints on how to use Optional with Optional<T> findById(ID id).

Generally, as you look for an entity by id, you want to return it or make a particular processing if that is not retrieved.

Here are three classical usage examples.

  1. Suppose that if the entity is found you want to get it otherwise you want to get a default value.

You could write :

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

or get a null default value if it makes sense (same behavior as before the API change) :

Foo foo = repository.findById(id)
                    .orElse(null);
  1. Suppose that if the entity is found you want to return it, else you want to throw an exception.

You could write :

return repository.findById(id)
        .orElseThrow(() -> new EntityNotFoundException(id));
  1. Suppose you want to apply a different processing according to if the entity is found or not (without necessarily throwing an exception).

You could write :

Optional<Foo> fooOptional = fooRepository.findById(id);
if (fooOptional.isPresent()) {
    Foo foo = fooOptional.get();
    // processing with foo ...
} else {
    // alternative processing....
}
Paulo Merson
  • 13,270
  • 8
  • 79
  • 72
davidxxx
  • 125,838
  • 23
  • 214
  • 215
  • thx david :) NotFoundEntity is extends RuntimeException { super("could not found " + id ); like this? – Hulk Choi Mar 16 '18 at 09:42
  • You are welcome. It is a "invented" exception. It can extend `Exception` or `RuntimeException` according to your need. But as said it is just an example :) – davidxxx Mar 16 '18 at 09:45
  • 5
    The first part is wrong: the method has not been moved to `QueryByExampleExecutor` it simply has been renamed to `findById(…)` returning an `Optional`. Also, always `….map(…)` on an `Optional`, never call `get()`. – Oliver Drotbohm Mar 16 '18 at 10:10
  • @Oliver Gierke "In fact, the method with the same behavior is still there in the new API but the method name has changed" I little reworded but I think that that is right and that is the most important information. – davidxxx Mar 17 '18 at 08:13
  • 1
    thnx davidxxx. You save me – Priyantha Jun 28 '18 at 10:40
  • @davidxxx, what if I don't want to findById(...) but instead find by some other field/criteria e.g. name or something else? – Artanis Zeratul Sep 19 '18 at 04:34
  • @Artanis Zeratul In this case you don't want to use `findById()` but using FindOne(Example), findAll(Example) or declare a query method in your Repository interface that allows Spring Data to create the query for you (https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods). If the criteria of search varies criteria and specification are probably the more adapted ways. – davidxxx Sep 19 '18 at 12:50
  • I can add that this is a change made since version Spring 2.0 M3 (https://jira.spring.io/browse/DATACMNS-944) – Arenas V. Dec 02 '18 at 16:24
  • 2
    Maybe EntityNotFoundException rather than NotFoundEntity is better. – zhouji Feb 21 '19 at 07:24
  • 1
    @zhouji Agreed and updated. Besides it is defined in the `javax.persistence` API as a `RuntimeException`. So makes really sense. – davidxxx Feb 21 '19 at 18:30
  • this was a hurdle for me also thanks for explanation. – nightfury Mar 10 '19 at 16:18
  • Thank you very much david, that is wonderful explanation! – Vaclav Vlcek Feb 17 '22 at 17:56
13

The method has been renamed to findById(…) returning an Optional so that you have to handle absence yourself:

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
Oliver Drotbohm
  • 80,157
  • 18
  • 225
  • 211
5

Indeed, in the latest version of Spring Data, findOne returns an optional. If you want to retrieve the object from the Optional, you can simply use get() on the Optional. First of all though, a repository should return the optional to a service, which then handles the case in which the optional is empty. afterwards, the service should return the object to the controller.

Claudiu Guja
  • 290
  • 1
  • 3
  • 12
  • 2
    Recommending to call `….get()` on an `Optional` is bad advice. `Optional` has dedicated methods to process the value. – Oliver Drotbohm Mar 16 '18 at 10:14
  • 2
    indeed, for the purpose of handling the value, there are better methods, such as .orElse() or .orElseThrow(). But for simply retrieving the item without checking whether the repository call returned the desired object, .get() should just about do it – Claudiu Guja Mar 16 '18 at 10:51
  • @OliverDrotbohm Optionals dont make sense when all you can do is throw a runtime exception and abort the code. In those instances I've seen developers inadvertently swallow bugs/exceptions/errors by using flatMap and ignoring the case that the object is gone. Where the code can't proceed without that object then get makes perfect sense and others will just mask the error. Its no different than checked exceptions vs unchecked. If theres nothing that can be done than a runtime/null pointer is fine so call .get(). If theres another course of action the user can take orElse makes sense. – Usman Mutawakil Jun 30 '22 at 21:52
4

I always write a default method "findByIdOrError" in widely used CrudRepository repos/interfaces.

@Repository 
public interface RequestRepository extends CrudRepository<Request, Integer> {

    default Request findByIdOrError(Integer id) {
        return findById(id).orElseThrow(EntityNotFoundException::new);
    } 
}
Sankarganesh Eswaran
  • 10,076
  • 3
  • 23
  • 24
  • But this returns a Request object. How should I transform this into the Entity I am serching for (if its there)? – sujumayas Jun 29 '19 at 22:49
2

Optional api provides methods for getting the values. You can check isPresent() for the presence of the value and then make a call to get() or you can make a call to get() chained with orElse() and provide a default value.

The last thing you can try doing is using @Query() over a custom method.

Prashant
  • 4,775
  • 3
  • 28
  • 47
2

The findOne method of the CrudRepository interface has been replaced by findById since version 2.0 of Spring Data Commons. you replace findOne(id) by:

findById(id).orElse(null)
Procrastinator
  • 2,526
  • 30
  • 27
  • 36
Mounir bkr
  • 1,069
  • 6
  • 6
0

Consider an User entity and UserRepository. In service package code like below.

Optional<User> resultUser = UserRepository.findById(userId);  //return Optional

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

Now you can access all the User entity attributes using getter.

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

like that.

Supun Sandaruwan
  • 1,814
  • 19
  • 19