In my web-apllication, in service-layout, I'm using proxy for the "restaurant" entity (FetchType.Lazy on "restaurant" field).
User user = userRepository.get(userId);
/*
Getting proxy here, not restaurant object
*/
Restaurant userRestaurantRef = user.getRestaurant();
if (userRestaurantRef != null){
restaurantRepository.decreaseRating(userRestaurantRef.getId());
}
restaurantRepository.increaseRating(restaurantId);
/*
"getReference" invokes "getOne()"
*/
user.setRestaurant(restaurantRepository.getReference(restaurantId));
userRepository.save(user);
After calling this method via controller in tests, all other RestaurantRepository's getting methods (such as findById()) returns proxy also.
But, if I called "findById()" method before my service's method, it's all OK.
For example:
mockMvc.perform(put(REST_URL + RESTAURANT1_ID)
.param("time", "10:30")
.with(userHttpBasic(USER)))
.andExpect(status().isNoContent());
Restaurant restaurant = restaurantRepository.get(RESTAURANT1_ID);
"restaurant" is PROXY
Restaurant restaurantBefore = restaurantRepository.get(RESTAURANT1_ID);
mockMvc.perform(put(REST_URL + RESTAURANT1_ID)
.param("time", "10:30")
.with(userHttpBasic(USER)))
.andExpect(status().isNoContent());
Restaurant restaurantAfter = restaurantRepository.get(RESTAURANT1_ID);
"restaurantAfter" is real Object
"get()" into repository:
@Override
public Restaurant get(int id) {
return repository.findById(id).orElse(null);
}
Both findById() and getOne() methods are used to retrieve an object from underlying datastore. But the underlying mechanism for retrieving records is different for both these methods, infact getOne() is lazy operation which does not even hit the database.
Its findById method retrieves an entity by its id. The return value is Optional<T> . Optional<T> is a container object which may or may not contain a non-null value. If a value is present, isPresent returns true and get returns the value.
As a consequence, findById() returns the actual object and getById returns a reference of the entity.
The getOne() method is used to retrieves an entity based on id and it is available in the JpaRepository interface.
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:
findById(id)
first and then getOne(id)
returns the same entity object for both getOne(id)
first and then findById(id)
returns the same proxy for bothThat'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:
@Transactional
when not need. Transactions can be managed manually too. Here are some good articles on that subject:
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
}
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With