When use getOne and findOne methods Spring Data JPA
The basic difference is that getOne
is lazy loaded and findOne
is not.
Consider the following example:
public static String NON_EXISTING_ID = -1;
...
MyEntity getEnt = myEntityRepository.getOne(NON_EXISTING_ID);
MyEntity findEnt = myEntityRepository.findOne(NON_EXISTING_ID);
if(findEnt != null) {
findEnt.getText(); // findEnt is null - this code is not executed
}
if(getEnt != null) {
getEnt.getText(); // Throws exception - no data found, BUT getEnt is not null!!!
}
TL;DR
T findOne(ID id)
(name in the old API) / Optional<T> findById(ID id)
(name in the new API) relies on EntityManager.find()
that performs an entity eager loading.
T getOne(ID id)
relies on EntityManager.getReference()
that performs an entity lazy loading. So to ensure the effective loading of the entity, invoking a method on it is required.
findOne()/findById()
is really more clear and simple to use than getOne()
.
So in the very most of cases, favor findOne()/findById()
over getOne()
.
API Change
From at least, the 2.0
version, Spring-Data-Jpa
modified findOne()
.
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 which 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 to that as 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 choice is now between Optional<T> findById(ID id)
and T getOne(ID id)
.
Two distinct methods that rely on two distinct JPA EntityManager retrieval methods
1) The Optional<T> findById(ID id)
javadoc states that it :
Retrieves an entity by its id.
As we look into the implementation, we can see that it relies on EntityManager.find()
to do the retrieval :
public Optional<T> findById(ID id) {
Assert.notNull(id, ID_MUST_NOT_BE_NULL);
Class<T> domainType = getDomainClass();
if (metadata == null) {
return Optional.ofNullable(em.find(domainType, id));
}
LockModeType type = metadata.getLockModeType();
Map<String, Object> hints = getQueryHints().withFetchGraphs(em).asMap();
return Optional.ofNullable(type == null ? em.find(domainType, id, hints) : em.find(domainType, id, type, hints));
}
And here em.find()
is an EntityManager
method declared as :
public <T> T find(Class<T> entityClass, Object primaryKey,
Map<String, Object> properties);
Its javadoc states :
Find by primary key, using the specified properties
So, retrieving a loaded entity seems expected.
2) While the T getOne(ID id)
javadoc states (emphasis is mine) :
Returns a reference to the entity with the given identifier.
In fact, the reference terminology is really board and JPA API doesn't specify any getOne()
method.
So the best thing to do to understand what the Spring wrapper does is looking into the implementation :
@Override
public T getOne(ID id) {
Assert.notNull(id, ID_MUST_NOT_BE_NULL);
return em.getReference(getDomainClass(), id);
}
Here em.getReference()
is an EntityManager
method declared as :
public <T> T getReference(Class<T> entityClass,
Object primaryKey);
And fortunately, the EntityManager
javadoc defined better its intention (emphasis is mine) :
Get an instance, whose state may be lazily fetched. If the requested instance does not exist in the database, the EntityNotFoundException is thrown when the instance state is first accessed. (The persistence provider runtime is permitted to throw the EntityNotFoundException when getReference is called.) The application should not expect that the instance state will be available upon detachment, unless it was accessed by the application while the entity manager was open.
So, invoking getOne()
may return a lazily fetched entity.
Here, the lazy fetching doesn't refer to relationships of the entity but the entity itself.
It means that if we invoke getOne()
and then the Persistence context is closed, the entity may be never loaded and so the result is really unpredictable.
For example if the proxy object is serialized, you could get a null
reference as serialized result or if a method is invoked on the proxy object, an exception such as LazyInitializationException
is thrown.
So in this kind of situation, the throw of EntityNotFoundException
that is the main reason to use getOne()
to handle an instance that does not exist in the database as an error situation may be never performed while the entity is not existing.
In any case, to ensure its loading you have to manipulate the entity while the session is opened. You can do it by invoking any method on the entity.
Or a better alternative use findById(ID id)
instead of.
Why a so unclear API ?
To finish, two questions for Spring-Data-JPA developers:
why not having a clearer documentation for
getOne()
? Entity lazy loading is really not a detail.why do you need to introduce
getOne()
to wrapEM.getReference()
?
Why not simply stick to the wrapped method :getReference()
? This EM method is really very particular whilegetOne()
conveys a so simple processing.