在N + 1场景中使用@NamedEntityGraph更有选择地加载JPA实体

N + 1问题是使用ORM解决方案时的常见问题。 当您将某些@OneToMany关系的fetchType设置为lazy时,会发生这种情况,以便仅在访问Set / List时才加载子实体。 假设我们有一个具有两个关系的Customer实体:每个客户的一组订单和一组地址。

@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<OrderEntity> orders;@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<AddressEntity> addresses;

要加载所有客户,我们可以发出以下JPQL语句,然后加载每个客户的所有订单:

List<CustomerEntity> resultList = entityManager.createQuery("SELECT c FROM CustomerEntity AS c", CustomerEntity.class).getResultList();
for(CustomerEntity customerEntity : resultList) {Set<OrderEntity> orders = customerEntity.getOrders();for(OrderEntity orderEntity : orders) {...}
}

Hibernate 4.3.5(随JBoss AS Wildfly 8.1.0CR2一起提供)将从数据库中仅为两个(!)客户生成以下一系列SQL语句:

Hibernate: selectcustomeren0_.id as id1_1_,customeren0_.name as name2_1_,customeren0_.numberOfPurchases as numberOf3_1_ fromCustomerEntity customeren0_
Hibernate: selectorders0_.CUSTOMER_ID as CUSTOMER4_1_0_,orders0_.id as id1_2_0_,orders0_.id as id1_2_1_,orders0_.campaignId as campaign2_2_1_,orders0_.CUSTOMER_ID as CUSTOMER4_2_1_,orders0_.timestamp as timestam3_2_1_ fromOrderEntity orders0_ whereorders0_.CUSTOMER_ID=?
Hibernate: selectorders0_.CUSTOMER_ID as CUSTOMER4_1_0_,orders0_.id as id1_2_0_,orders0_.id as id1_2_1_,orders0_.campaignId as campaign2_2_1_,orders0_.CUSTOMER_ID as CUSTOMER4_2_1_,orders0_.timestamp as timestam3_2_1_ fromOrderEntity orders0_ whereorders0_.CUSTOMER_ID=?

如我们所见,第一个查询从表CustomerEntity中选择所有客户。 接下来的两个选择先提取,然后在第一个查询中加载我们已加载的每个客户的订单。 当我们有100个客户而不是2个客户时,我们将获得101个查询。 一个初始查询可加载所有客户,然后针对100个客户中的每个客户,另外查询一个订单。 这就是为什么将此问题称为N + 1的原因。

解决此问题的常见习惯是强制ORM生成内部联接查询。 在JPQL中,可以通过使用JOIN FETCH子句来完成,如以下代码片段所示:

entityManager.createQuery("SELECT c FROM CustomerEntity AS c JOIN FETCH c.orders AS o", CustomerEntity.class).getResultList();

正如预期的那样,ORM现在使用OrderEntity表生成一个内部联接,因此只需要一个SQL语句即可加载所有数据:

selectcustomeren0_.id as id1_0_0_,orders1_.id as id1_1_1_,customeren0_.name as name2_0_0_,orders1_.campaignId as campaign2_1_1_,orders1_.CUSTOMER_ID as CUSTOMER4_1_1_,orders1_.timestamp as timestam3_1_1_,orders1_.CUSTOMER_ID as CUSTOMER4_0_0__,orders1_.id as id1_1_0__
fromCustomerEntity customeren0_
inner joinOrderEntity orders1_on customeren0_.id=orders1_.CUSTOMER_ID

在您知道必须为每个客户加载所有订单的情况下,JOIN FETCH子句将SQL语句的数量从N + 1减少到1。这当然具有缺点,即您现在要转移一个订单的所有订单。客户一次又一次的客户数据(由于查询中的其他客户列)。

JPA规范引入了2.1版,即所谓的NamedEntityGraphs。 此注释使您可以描述JPQL查询应加载的图形,而不是JOIN FETCH子句可以加载的图形,从而为N + 1问题提供了另一种解决方案。 下面的示例演示了我们的客户实体的NamedEntityGraph,该实体仅加载客户名称及其订单。 订单在子图中的orderGraph中有更详细的描述。 在这里,我们看到我们只想加载订单的字段ID和CampaignId。

@NamedEntityGraph(name = "CustomersWithOrderId",attributeNodes = {@NamedAttributeNode(value = "name"),@NamedAttributeNode(value = "orders", subgraph = "ordersGraph")},subgraphs = {@NamedSubgraph(name = "ordersGraph",attributeNodes = {@NamedAttributeNode(value = "id"),@NamedAttributeNode(value = "campaignId")})}
)

在通过EntityManager使用其名称加载了NamedEntityGraph后,将其作为JPQL查询的提示:

EntityGraph entityGraph = entityManager.getEntityGraph("CustomersWithOrderId");
entityManager.createQuery("SELECT c FROM CustomerEntity AS c", CustomerEntity.class).setHint("javax.persistence.fetchgraph", entityGraph).getResultList();

Hibernate从4.3.0.CR1版本开始支持@NamedEntityGraph注释,并为上面显示的JPQL查询创建以下SQL语句:

Hibernate: selectcustomeren0_.id as id1_1_0_,orders1_.id as id1_2_1_,customeren0_.name as name2_1_0_,customeren0_.numberOfPurchases as numberOf3_1_0_,orders1_.campaignId as campaign2_2_1_,orders1_.CUSTOMER_ID as CUSTOMER4_2_1_,orders1_.timestamp as timestam3_2_1_,orders1_.CUSTOMER_ID as CUSTOMER4_1_0__,orders1_.id as id1_2_0__ fromCustomerEntity customeren0_ left outer joinOrderEntity orders1_ on customeren0_.id=orders1_.CUSTOMER_ID

我们看到,Hibernate不会发出N + 1查询,而是@NamedEntityGraph注释强制Hibernate为每个左外部联接加载订单。 当然,这与FETCH JOIN子句有微妙的区别,在子句中,Hibernate创建了一个内部联接。 与FETCH JOIN子句相反,左外部联接还将加载不存在订单的客户,在FETCH JOIN子句中,我们仅加载至少具有一个订单的客户。

有趣的是,Hibernate加载的负载比表CustomerEntity和OrderEntity的指定属性更多。 由于这与@NamedEntityGraph的规范(第3.7.4节)相冲突,因此我为此创建了一个JIRA问题 。

结论

我们已经看到,在JPA 2.1中,我们为N + 1问题提供了两种解决方案:我们可以使用FETCH JOIN子句来急切地获取@OneToMany关系,这将导致内部联接,或者我们可以使用@NamedEntityGraph功能我们指定通过左外部联接加载哪个@OneToMany关系。

翻译自: https://www.javacodegeeks.com/2014/07/using-namedentitygraph-to-load-jpa-entities-more-selectively-in-n1-scenarios.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/363546.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JetBrains系列IDE快捷键大全(转载)

编辑 快捷键组合说明Ctrl Space代码自动完成提示&#xff08;选择&#xff09;Alt Enter显示意图动作和快速修复Ctrl P参数信息 &#xff08;在调用方法参数忘记的时候&#xff0c;提示&#xff09;Ctrl Q快速查找文件&#xff0c;可以查找当前类定义的文件等Ctrl 鼠标滑过…

Vs Code 配置C/C++ 开发环境

第一步&#xff1a;下载 Vs Code 点击链接下载Vs Code 下载版本 并安装 https://code.visualstudio.com/ 点击 Download for Windwos 安装时 如图&#xff1a;请一定要勾选 添加到PATH (环境变量&#xff09; 其他选项可根据个人需要选配 但建议全部勾选 第二步&#xf…

28. css样式中px转rem

Vue3:脚手架配置 https://blog.csdn.net/weixin_41424247/article/details/80867351 与原来的vue-cli 2.x版本不同的是&#xff1a;如果使用最新版本的vue/cli初始化vue项目时&#xff0c;通常看不到webpack的配制文件。而在原来的2.x版本&#xff0c;我们可以在utils.js中轻…

集合已修改;可能无法执行枚举操作。

在对某个List进行遍历的同时&#xff0c;需要对其中的Item进行删除操作。 会提示错误&#xff1a;集合已修改&#xff1b;可能无法执行枚举操作 Codeforeach (VirtualTDate vtDate in tempList){ if (vtDate.Date itemTime.Date) { tempList.Remove(vtDate); …

UI测试脸型软件,App脸型美化剖析|UI-影视-其他|观点|freshoil - 原创文章 - 站酷 (ZCOOL)...

本文基于市面上多款App的美颜效果&#xff0c;做了一个对比分析&#xff0c;整理出一个可以指导美颜调教的参考规范。研究的几个要点如下&#xff1a;1.通过对 某陌、某音、某Y、某他相机、某天P图的效果对比分析2.本次只针对默认效果做对比(某Y无默认则选择自然)3.统一使用前置…

使用Docker,Chef和Amazon OpsWorks进行集群范围的Java / Scala应用程序部署

Docker非常适合在单个节点上运行隔离的容器。 但是&#xff0c;大多数软件系统都在多个节点上运行&#xff0c;因此&#xff0c;除了Docker之外&#xff0c;我们还需要某种方法来指定哪些容器应在哪些节点上运行。 我要解决的特定问题如下&#xff1a;我有两个Scala守护程序&a…

根据输入成绩显示成绩等级(新手)

//导入包。 import java.util.Scanner; //定义一个类。 public class zy238{    //公共静态的主方法。 public static void main(String[] args){ //打印提示。     System.out.println("请你输入成绩"); //为其创建变量。     Scanner sc new Scanner(Sy…

Tmux: 打造精致与实用并存的终端

由于最近需要经常 ssh 到远程环境&#xff0c;遂趁此折腾了一番 tmux。毕竟 工欲善其事&#xff0c;必先利其器 以下是我的配置文件地址&#xff0c;并在不断摸索与更新中。特别喜欢 solarized 主题&#xff0c;于是参考它配了状态栏的主题。在后边我会列出一些平时使用的技巧&…

8、路由 router

路由:router 用户功能 /user ----> index.html /user/login ----> login.html /user/reg ----> reg.html /user userRouter > express.Router(); app.use("/user",userRouter); 新闻功能 /news ----> index.html /news/edit ----> edit.ht…

服务器装虚拟化平台,vmware服务器虚拟化方案(vmware虚拟化平台部署)

服务器虚拟化平台方案主要的有三种&#xff0c;特点分别如下&#xff1a;1、思杰Citrix XenServer :XenCenter是Citrix的虚拟化图形接口管理工具&#xff0c;可在同一界面&#xff0c;管理多台的XenServer服务。以前见过一台服务器安装虚拟服务器&#xff0c;然后可以装N个系统…

一个逐步“优化”的范例程序(转)

reference URL&#xff1a;http://www.tracefact.net/Software-Design/A-Sample-Design.aspx本文是《Object-Oriented Analysis and Design》一书第一章和第五章的读书笔记。我对书中提供的一个范例程序进行了总结和整理&#xff0c;通过逐步优化这个乐器管理的范例程序&#x…

Java SE 8新特性导览:使用Lambda Expression进行函数式编程

“ Java SE 8新功能浏览 ”系列的这篇文章将深入了解Lambda表达式 。 我将向您展示Lambda表达式的几种不同用法。 它们都具有功能接口的共同实现。 我将解释编译器如何从代码中推断信息&#xff0c;例如特定类型的变量以及后台实际发生的情况。 在上一篇文章“ Java SE 8新功能…

JS 数据转换

转换成字符串类型 toString() var num 5;console.log(num.toString()); String() String()函数存在的意义&#xff1a;有些值没有toString()&#xff0c;这个时候可以使用String()。比如&#xff1a;undefined和null 拼接字符串方式 num ""&#xff0c;当 两边一…

凭借K2 SmartObject框架,在SharePoint中集成数据

随着SharePoint 2013的发布&#xff0c;Microsoft已提供Business Connectivity Services&#xff08;BCS&#xff09;增强功能以及外部列表功能&#xff0c;确保您可以更简单地在SharePoint环境下从外部数据源提取数据。针对诸如服务台票务应用或销售仪表盘等实施解决方案&…

浙大计算机专硕培养方案,浙江大学硕士研究生培养方案

《浙江大学硕士研究生培养方案》由会员分享&#xff0c;可在线阅读&#xff0c;更多相关《浙江大学硕士研究生培养方案(3页珍藏版)》请在人人文库网上搜索。1、浙江大学硕士研究生培养方案医 学院 肿瘤学 专业(代码&#xff1a; 100214 )(一级学科&#xff1a; 临床医学 )一、培…

停止尝试使用内部DB框架模拟SQL OFFSET分页!

我敢肯定&#xff0c;到目前为止&#xff0c;您已经以多种方式弄错了。 而且您可能很快将无法正确处理。 那么&#xff0c;当您可以实施业务逻辑时&#xff0c;为什么还要在SQL调整上浪费您的宝贵时间呢&#xff1f; 让我解释… 直到最近的SQL&#xff1a;2008标准 &#xff0…

leetcode 20. Valid Parentheses 、32. Longest Valid Parentheses 、301. Remove Invalid Parentheses

20. Valid Parentheses 错误解法&#xff1a; "[])"就会报错&#xff0c;没考虑到出现)、]、}时&#xff0c;stack为空的情况&#xff0c;这种情况也无法匹配 class Solution { public:bool isValid(string s) {if(s.empty())return false;stack<char> st;st.…

和朱晔一起复习Java并发(五):并发容器和同步器

和朱晔一起复习Java并发&#xff08;五&#xff09;&#xff1a;并发容器和同步器 本节我们先会来复习一下java.util.concurrent下面的一些并发容器&#xff0c;然后再会来简单看一下各种同步器。 ConcurrentHashMap和ConcurrentSkipListMap的性能 首先&#xff0c;我们来测试一…

树莓派3显示服务器SSH拒绝了密码,脚本封杀尝试树莓派SSH密码的来源IP

树莓派整天开着&#xff0c;如果用缺省SSH端口对外开放&#xff0c;就会经常遇到扫描SSH密码的肉鸡。虽然密码不是很简单&#xff0c;但还是感觉很不安全的。系统的ssh登录日志文件在&#xff1a;/var/log/auth.log&#xff0c;登录失败时会记录以下格式的日志&#xff1a;Mar …

6-1 数组函数练习

1、引用 /* var num 10;function show(num){num ;alert(num);}alert(num); //10show(num); //11 num numalert(num); //10*//*引用*//*var arr [1, 2, 3, 4];function show(arr){arr.push("hello");alert(arr);}alert(arr); //[1, 2, 3, 4]show(arr)…