简体   繁体   English

为什么“findById()”在同一实体上调用 getOne() 后返回代理?

[英]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:回到你的例子:

  • call findById(id) first and then getOne(id) returns the same entity object for both首先调用findById(id)然后getOne(id)为两者返回相同的实体 object
  • call getOne(id) first and then findById(id) returns the same proxy for both首先调用getOne(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:作为该问题的解决方法,我可以建议:

  1. Do not acquire the same entity twice in the same transactional method.不要以相同的交易方式两次收购同一实体。 :) :)
  2. Avoid using @Transactional when not need.不需要时避免使用@Transactional Transactions can be managed manually too.交易也可以手动管理。 Here are some good articles on that subject:这里有一些关于这个主题的好文章:
  3. 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
    }
  1. Similar to the 3rd approach, but instead of removing a single object from the cache, remove all at once using the clear() method:类似于第 3 种方法,但不是从缓存中删除单个 object,而是使用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.

相关问题 调用dao.findByID(id)后,实体属性不变 - Entity properties are not changing after calling dao.findByID(id) 为什么 spring 数据 redis findById 在升级到版本 2.3.2.RELEASE 后在 Optional 中返回 null 值 - Why spring data redis findById returns null values in Optional after upgrading to version 2.3.2.RELEASE 在休眠实体上调用merge返回Javassist代理对象? - Calling merge on a hibernate entity returns Javassist proxy objects? 为什么 findById 响应未找到 id 但 findAll 使用此 id 返回 object - Why findById response is id not found but findAll returns object with this id Spring @Entity注释findById - Spring @Entity Annotation findById “一对多映射”场景中子实体上的findById操作返回递归子实体。 需要解决方案 - A findById operation on the child entity in the 'One to many mapping' scenario returns recursive child entities. Need solution CrudRepository findById 返回 null mysql - CrudRepository findById returns null mysql 为什么在同一个stream上调用两个终端操作后不抛出IllegalStateException? - Why is IllegalStateException not thrown after calling two terminal operations on the same stream? Hibernate从同一个请求中返回相同的实体,但在一种情况下它是代理的,在第二种情况下它不是。为什么? - Hibernate returns same entity from same request, but in one case it is proxied and in second case it is not. Why? 为什么Spring数据存储库上的getOne(...)不会抛出EntityNotFoundException? - Why does getOne(…) on a Spring Data repository not throw an EntityNotFoundException?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM