Do you have @Transactional
annotation on the method or service class itself?
This could explain the observed behavior.
When a method is executed in a transaction, entities acquired or merged/saved from/to the database are cached until the end of the transaction (usually the end of the method). That means that any call for entity with same ID will be returned directly from the cache and will not hit the database.
Here are some articles on Hibernate's caching and proxies:
Back to your example:
- call
findById(id)
first and then getOne(id)
returns the same entity object for both
- call
getOne(id)
first and then findById(id)
returns the same proxy for both
That's because they share the same id
and are executed in the same transaction.
Documentation on getOne()
states that it could return an instance
instead of reference (HibernateProxy), so having it returning an entity could be expected:
T getOne(ID id)
Returns a reference to the entity with the given identifier.
Depending on how the JPA persistence provider is implemented this is very likely
to always return an instance and throw an EntityNotFoundException on
first access. Some of them will reject invalid identifiers immediately.
Parameters:
id - must not be null.
Returns:
a reference to the entity with the given identifier.
Documentation on findById()
from the other hand does not have any hints in the direction that it could return anything but Optional
of entity or empty Optional
:
Optional findById(ID id)
Retrieves an entity by its id.
Parameters: id - must not be null.
Returns: the entity with the given id or Optional#empty() if none found
I've spend some time looking for a better explanation, but failed to find one so I'm not sure if it is a bug in the implementation of findById()
or just a not (well) documented feature.
As workarounds to the problem I could suggest:
- Do not acquire the same entity twice in the same transactional method. :)
- Avoid using
@Transactional
when not need. Transactions can be managed manually too. Here are some good articles on that subject:
- Detach first loaded entity/proxy before (re-)loading using the other method:
import javax.persistence.EntityManager;
import org.springframework.transaction.annotation.Transactional;
@Transactional
@Service
public class SomeServiceImpl implements SomeService {
private final SomeRepository repository;
private final EntityManager entityManager;
// constructor, autowiring
@Override
public void someMethod(long id) {
SomeEntity getOne = repository.getOne(id); // Proxy -> added to cache
entityManager.detach(getOne); // removes getOne from the cache
SomeEntity findById = repository.findById(id).get(); // Entity from the DB
}
- Similar to the 3rd approach, but instead of removing a single object from the cache, remove all at once using the
clear()
method:
import javax.persistence.EntityManager;
import org.springframework.transaction.annotation.Transactional;
@Transactional
@Service
public class SomeServiceImpl implements SomeService {
private final SomeRepository repository;
private final EntityManager entityManager;
// constructor, autowiring
@Override
public void someMethod(long id) {
SomeEntity getOne = repository.getOne(id); // Proxy -> added to cache
entityManager.clear(); // clears the cache
SomeEntity findById = repository.findById(id).get(); // Entity from the DB
}
Related articles:
EDIT:
Here is a simple project demonstrating the problem or the feature (depending on the point of view).