I have an use case where it calls the following:
@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
public UserControl getUserControlById(Integer id){
return this.userControlRepository.getOne(id);
}
Observe the @Transactional
has Propagation.REQUIRES_NEW and the repository uses getOne. When I run the app, I receive the following error message:
Exception in thread "main" org.hibernate.LazyInitializationException:
could not initialize proxy - no Session
...
But If I change the getOne(id)
by findOne(id)
all works fine.
BTW, just before the use case calls the getUserControlById method, it already has called the insertUserControl method
@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
public UserControl insertUserControl(UserControl userControl) {
return this.userControlRepository.save(userControl);
}
Both methods are Propagation.REQUIRES_NEW because I am doing a simple audit control.
I use the getOne
method because it is defined in the JpaRepository interface and my Repository interface extends from there, I am working with JPA of course.
The JpaRepository interface extends from CrudRepository.
The findOne(id)
method is defined in CrudRepository
.
My questions are:
- Why fail the
getOne(id)
method? - When I should use the
getOne(id)
method?
I am working with other repositories and all use the getOne(id)
method and all work fine, only when I use the Propagation.REQUIRES_NEW it fails.
According with the getOne API:
Returns a reference to the entity with the given identifier.
According with the findOne API:
Retrieves an entity by its id.
-
When I should use the
findOne(id)
method? -
What method is recommended to be used?
Best Answer
TL;DR
T findOne(ID id)
(name in the old API) /Optional<T> findById(ID id)
(name in the new API) relies onEntityManager.find()
that performs an entity eager loading.T getOne(ID id)
relies onEntityManager.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 thangetOne()
.So in the very most of cases, favor
findOne()/findById()
overgetOne()
.API Change
From at least, the
2.0
version,Spring-Data-Jpa
modifiedfindOne()
.Previously, it was defined in the
CrudRepository
interface as :Now, the single
findOne()
method that you will find inCrudRepository
is which one defined in theQueryByExampleExecutor
interface as :That is implemented finally by
SimpleJpaRepository
, the default implementation of theCrudRepository
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()
tofindById()
in theCrudRepository
interface :Now it returns an
Optional
. Which is not so bad to preventNullPointerException
.So, the actual choice is now between
Optional<T> findById(ID id)
andT 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 :As we look into the implementation, we can see that it relies on
EntityManager.find()
to do the retrieval :And here
em.find()
is anEntityManager
method declared as :Its javadoc states :
So, retrieving a loaded entity seems expected.
2) While the
T getOne(ID id)
javadoc states (emphasis is mine) :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 :
Here
em.getReference()
is anEntityManager
method declared as :And fortunately, the
EntityManager
javadoc defined better its intention (emphasis is mine) :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 asLazyInitializationException
is thrown.So in this kind of situation, the throw of
EntityNotFoundException
that is the main reason to usegetOne()
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.