[英]Why “findById()” returns proxy after calling getOne() on same entity?
In my web-apllication, in service-layout, I'm using proxy for the "restaurant" entity (FetchType.Lazy on "restaurant" field).在我的网络应用程序中,在服务布局中,我使用“餐厅”实体的代理(“餐厅”字段上的 FetchType.Lazy)。
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.在测试中通过 controller 调用此方法后,所有其他 RestaurantRepository 的获取方法(例如 findById())也返回代理。
But, if I called "findById()" method before my service's method, it's all OK.但是,如果我在我的服务方法之前调用了“findById()”方法,那就没问题了。
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 "restaurantAfter" 是真实的 Object
"get()" into repository: “get()”进入存储库:
@Override
public Restaurant get(int id) {
return repository.findById(id).orElse(null);
}
Do you have @Transactional
annotation on the method or service class itself?您对方法或服务 class 本身是否有
@Transactional
注释?
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.
这意味着对具有相同 ID 的实体的任何调用都将直接从缓存中返回,并且不会命中数据库。
Here are some articles on Hibernate's caching and proxies:这里有一些关于 Hibernate 的缓存和代理的文章:
Back to your example:回到你的例子:
findById(id)
first and then getOne(id)
returns the same entity object for bothfindById(id)
然后getOne(id)
为两者返回相同的实体 objectgetOne(id)
first and then findById(id)
returns the same proxy for bothgetOne(id)
然后findById(id)
为两者返回相同的代理That's because they share the same id
and are executed in the same transaction.那是因为它们共享相同的
id
并在同一事务中执行。
Documentation on getOne()
states that it could return an instance
instead of reference (HibernateProxy), so having it returning an entity could be expected: getOne()
上的文档指出它可以返回an instance
而不是引用 (HibernateProxy),因此可以预期它返回一个实体:
T getOne(ID id)
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.
根据 JPA 持久性提供程序的实现方式,这很可能总是返回一个实例并在首次访问时抛出 EntityNotFoundException。 Some of them will reject invalid identifiers immediately.
他们中的一些人会立即拒绝无效的标识符。
Parameters: id - must not be null.
参数:id - 不得为 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
:另一方面,关于
findById()
的文档没有任何提示,它可以返回任何东西,但Optional
的实体或空的Optional
:
Optional findById(ID id)
可选的 findById(ID id)
Retrieves an entity by its id.
通过 id 检索实体。
Parameters: id - must not be null.
参数:id - 不得为 null。
Returns: the entity with the given id or Optional#empty() if none found
返回:具有给定 id 的实体,如果没有找到,则返回 Optional#empty()
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.我花了一些时间寻找更好的解释,但没有找到一个,所以我不确定它是
findById()
实现中的一个错误,还是只是一个没有(很好)记录的功能。
As workarounds to the problem I could suggest:作为该问题的解决方法,我可以建议:
@Transactional
when not need.@Transactional
。 Transactions can be managed manually too.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:clear()
方法一次性删除: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).这是一个演示问题或功能的简单项目(取决于观点)。
Some extension to the - already accepted - answer:对 - 已经接受 - 答案的一些扩展:
If you use Spring Boot then it automatically enable the Open Session In View
filter, which basically works as a transaction for each request.如果您使用 Spring Boot,那么它会自动启用
Open Session In View
过滤器,它基本上作为每个请求的事务。
If you want to turn off this feature add the following line to the application.properties:如果要关闭此功能,请将以下行添加到 application.properties:
spring.jpa.open-in-view=false
OSIV is really a bad idea from a performance and scalability perspective.从性能和可扩展性的角度来看,OSIV确实是个坏主意。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.