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

Iva*_*van 5 java hibernate spring-data-jpa

在我的网络应用程序中,在服务布局中,我使用“餐厅”实体的代理(“餐厅”字段上的 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);
Run Code Online (Sandbox Code Playgroud)

在测试中通过控制器调用此方法后,所有其他 RestaurantRepository 的获取方法(例如 findById())也返回代理。

但是,如果我在服务的方法之前调用“findById()”方法,则一切正常。

例如:

mockMvc.perform(put(REST_URL + RESTAURANT1_ID)
                .param("time", "10:30")
                .with(userHttpBasic(USER)))
                .andExpect(status().isNoContent());

Restaurant restaurant = restaurantRepository.get(RESTAURANT1_ID);
Run Code Online (Sandbox Code Playgroud)

“餐厅”是代理

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);
Run Code Online (Sandbox Code Playgroud)

“restaurantAfter”是真正的对象

“get()”进入存储库:

    @Override
    public Restaurant get(int id) {
        return repository.findById(id).orElse(null);
    }
Run Code Online (Sandbox Code Playgroud)

Mar*_*nBG 5

您是否@Transactional对方法或服务类本身进行了注释?

这可以解释观察到的行为。

当在事务中执行方法时,从/向数据库获取或合并/保存的实体将被缓存,直到事务结束(通常是方法结束)。这意味着对具有相同 ID 的实体的任何调用都将直接从缓存中返回,并且不会访问数据库。

这里有一些关于 Hibernate 的缓存和代理的文章:

回到你的例子:

  • findById(id)首先调用,然后getOne(id)为两者返回相同的实体对象
  • getOne(id)首先调用,然后findById(id)为两者返回相同的代理

那是因为它们共享相同的内容id并在同一个事务中执行。

有关getOne()说明它可以返回an instance而不是引用(HibernateProxy)的文档,因此可以预期它返回一个实体:

T getOne(ID id)

返回对具有给定标识符的实体的引用。

根据 JPA 持久性提供程序的实现方式,这很可能总是返回一个实例并在第一次访问时抛出 EntityNotFoundException。其中一些会立即拒绝无效的标识符。

参数: id - 不能为空。

返回: 对具有给定标识符的实体的引用。

findById()另一方面,文档没有任何关于它可以返回除Optionalentity 或 empty 之外的任何内容的提示Optional

可选的 findById(ID id)

通过其 id 检索实体。

参数: id - 不能为空。

返回: 具有给定 id 的实体或 Optional#empty() 如果没有找到

我花了一些时间寻找更好的解释,但没有找到,所以我不确定它是实现中的一个错误findById()还是一个没有(很好)记录的功能。

作为问题的解决方法,我可以建议:

  1. 不要在同一个事务方法中两次获取同一个实体。:)
  2. 避免@Transactional在不需要时使用。交易也可以手动管理。这里有一些关于这个主题的好文章:
  3. 使用另一种方法在(重新)加载之前分离第一个加载的实体/代理:
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
    }
Run Code Online (Sandbox Code Playgroud)
  1. 与第 3 种方法类似,但不是从缓存中删除单个对象,而是使用以下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
    }
Run Code Online (Sandbox Code Playgroud)

相关文章:


编辑:

这是一个演示问题或功能的简单项目(取决于观点)。