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

2024-02-29

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

在测试中通过控制器调用此方法后,所有其他 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);

“餐厅”是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”是真实的对象

“get()”进入存储库:

    @Override
    public Restaurant get(int id) {
        return repository.findById(id).orElse(null);
    }

你有@Transactional方法或服务类本身的注释?

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

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

以下是一些有关 Hibernate 缓存和代理的文章:

  • 通过示例了解 Hibernate 一级缓存 https://howtodoinjava.com/hibernate/understanding-hibernate-first-level-cache-with-example/
  • JPA 代理如何工作以及如何使用 Hibernate 取消代理 https://vladmihalcea.com/how-does-a-jpa-proxy-work-and-how-to-unproxy-it-with-hibernate/
  • 使用 JPA 和 Hibernate 初始化 LAZY 实体和集合代理的最佳方法 https://vladmihalcea.com/initialize-lazy-proxies-collections-jpa-hibernate/

回到你的例子:

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

那是因为他们有共同点id并在同一事务中执行。

有关的文档getOne()指出它could return an instance而不是引用(HibernateProxy),因此可以期望它返回一个实体:

T getOne(ID id)

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

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

参数: id - 不能为空。

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

有关的文档findById()另一方面,没有任何暗示它可以返回任何东西,但Optional实体或空Optional:

可选 findById(ID id)

通过 id 检索实体。

参数: id - 不能为空。

返回:具有给定 id 的实体,如果未找到,则为Optional#empty()

我花了一些时间寻找更好的解释,但未能找到,所以我不确定这是否是实现中的一个错误findById()或者只是一个没有(充分)记录的功能。

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

  1. 不要以相同的交易方式两次收购同一实体。 :)
  2. Avoid using @Transactional when not need. Transactions can be managed manually too. Here are some good articles on that subject:
    • 5 个常见的 Spring @Transactional 陷阱 https://codete.com/blog/5-common-spring-transactional-pitfalls/
    • Spring 事务传播模式 https://codete.com/blog/spring-transaction-propagation-modes/
    • Spring 陷阱:事务测试被认为是有害的 https://www.nurkiewicz.com/2011/11/spring-pitfalls-transactional-tests.html.
  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
    }
  1. 与第三种方法类似,但不是从缓存中删除单个对象,而是使用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
    }

相关文章:

  • 何时使用 getOne 和 findOne 方法 Spring Data JPA https://stackoverflow.com/questions/24482117/when-use-getone-and-findone-methods-spring-data-jpa
  • Hibernate 会话:evict() 和 merge() 示例 https://www.concretepage.com/hibernate/hibernate-session-evict-and-merge-example
  • Hibernate 中的clear()、evict() 和close() 方法 https://www.connect2java.com/tutorials/hibernate/clear-evict-and-close-methods-in-hibernate/
  • JPA - 从持久性上下文中分离实体实例 https://www.logicbig.com/tutorials/java-ee-tutorial/jpa/detaching.html
  • Spring Data JPA 中 getOne 和 findById 之间的区别? https://www.javacodemonk.com/difference-between-getone-and-findbyid-in-spring-data-jpa-3a96c3ff

EDIT:

这里有一个简单的项目 https://github.com/Martin-BG/jpa-transactional-oddities展示问题或功能(取决于观点)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

随机推荐

  • SQL Server 2000 中的交叉表查询

    我希望以前有人尝试过这一点 并且在我进一步之前可以得到一些建议 我希望在 sql server 2000 中生成类似于交叉表查询的内容 我有一个类似于以下的表结构 Item Item Parameter Parameter id item
  • 在我的 iOS 应用程序中实施新的 Google 地图 SDK

    更新 我刚刚收到一封来自 Google 的有关新 Google 地图 iOS SDK 的电子邮件 看来一切都已经解决了 我已成功为我的应用程序创建新的 API 密钥 还没有测试过 但看起来是正确的 他们派我来this https devel
  • 我应该何时以及如何使用 ThreadLocal 变量?

    我什么时候应该使用ThreadLocal https docs oracle com javase 8 docs api java lang ThreadLocal html多变的 它是如何使用的 一种可能 也是常见 的用途是当您有一些非线
  • PHP cURL:获取重定向目标,而不跟随它

    curl getinfo 函数返回大量有关 HTTP 请求结果的元数据 但是 由于某种原因 它不包含我目前想要的信息 如果请求返回 HTTP 重定向代码 则该信息是目标 URL 我没有使用 CURLOPT FOLLOWLOCATION 因为
  • 在 React Native 中使用 mobx 进行状态存储时无法导航到不同的导航菜单

    我对 Mobx 有点陌生 一般来说 我的反应是原生的 我正在尝试使用 mobx 在导航堆栈中实现状态值更改 以便当单击登录按钮时 状态中的值会发生更改 并且导航值参数令牌会更新为 mobx 存储中的最新值 但这不起作用 我收到错误错误任何导
  • 突出显示段落中的关键字

    我需要突出显示段落中的关键字 就像谷歌在其搜索结果中所做的那样 假设我有一个包含博客文章的 MySQL 数据库 当用户搜索某个关键字时 我希望返回包含这些关键字的帖子 但只显示帖子的一部分 包含搜索关键字的段落 并突出显示这些关键字 我的计
  • C、没有 malloc 的 fork 泄漏

    我试图了解内存分配如何在 fork 上工作 即使是静态或动态分配 我很难理解一些泄漏 如下所示 通过这个程序 include
  • Spark Dataframe 中的聚合数组类型

    我有一个 DataFrame 命令 Id Order Gender 1622 101330001 Male 1622 147678 Male 3837 1710544 Male 我想对 ID 和性
  • 字典中具有多个值的单个键

    我的代码中可以有一个全局字典吗 如下所示 group vowel aa ae ah ao eh er ey ih iy uh uw o consonant b ch d dh dx f g hh jh k l m n ng p r s sh
  • 如何在 xml 布局上使用伴随对象?

    我试图在布局内使用伴随对象属性 但编译器无法识别它 Kotlin 类 class MyClass companion object val SomeProperty hey XML布局
  • 如何在 R 中生成给定的均值、SD、偏度和峰度分布?

    是否可以在 R 中生成均值 SD 偏度和峰度已知的分布 到目前为止 最好的途径似乎是创建随机数并相应地对其进行转换 如果有一个专门用于生成可以适应的特定发行版的包 我还没有找到它 谢谢 SuppDists 包中有一个 Johnson 发行版
  • 使用 R 将 qicharts 图转换为 ggplot

    我的数据框看起来像这样 Datetime lt c 2015 09 29AM 2015 09 29PM 2015 09 30AM 2015 09 30PM 2015 10 01AM 2015 10 01PM 2015 10 02AM 201
  • 从通知中启动的活动按返回/主页退出应用程序

    我有一个从通知启动的活动 我使用 TaskStackBuilder 包含一个后退堆栈 以便当用户点击主页按钮 操作栏标题按钮 或使用后退键时 它将返回到应用程序 但是 它不是以这种方式工作的 而是回击或操作栏标题按钮总是导致应用程序关闭 就
  • JSON.parse 从 JSON 返回 [object]

    我正在使用一个名为的 npm 包request发出 http 请求 现在我想解析收到的数据 以提取经纬度并将其写入我的数据库 但到目前为止 我得到的控制台输出是 address components Object Object Object
  • C# using 和 Java import 之间的区别

    我知道在java中我们使用 星号 来导入包中的所有内容 例如 import java lang 那为什么我们不在C 中使用相同的 星号 来导入所有内容 有没有像java中那样的方法来导入所有内容 有什么区别 import java awt
  • 是否可以使用相同的 requestCode 和不同的 extras 创建多个 PendingIntents ?

    我在用着AlarmManager安排 1 到 35 个警报 取决于用户输入 当用户请求安排新警报时 我需要取消当前警报 因此我使用相同的 requestCode 创建所有警报 该请求代码在final多变的 clear remaining a
  • 如何在 UWP 应用中播放 JS 的声音?

    我正在开发一个 UWP 其中包含一个 Web 应用程序 该应用程序具有一些调用一些 C 函数的 JS 函数 现在 我正在尝试播放我存储在 UWP 应用程序的 Assets 文件夹中的声音 这是我想要发挥的功能Windows 运行时组件 pu
  • html5本地数据库位于客户端计算机上的哪里?

    我正在不同的浏览器 Firefox Opera Safari 和 Chrome 上使用 html5 本地存储 我只是想知道我使用 创建的本地数据库的位置在哪里 开放数据库 我可以更改该数据库的位置吗 Gath 它将存储在用户的配置文件目录中
  • jQuery 的 ajax 成功函数的额外参数

    我正在使用以下代码获取 XML 文件 function getMaps toLoad loadMaps length for var i 0 i lt loadMaps length i ajax type GET url loadMaps
  • 为什么“findById()”在同一实体上调用 getOne() 后返回代理?

    在我的网络应用程序中 在服务布局中 我使用 餐厅 实体的代理 餐厅 字段中的 FetchType Lazy User user userRepository get userId Getting proxy here not restaur