在N + 1场景中使用@NamedEntityGraph更有选择地加载JPA实体
N + 1問(wèn)題是使用ORM解決方案時(shí)的常見(jiàn)問(wèn)題。 當(dāng)您將某些@OneToMany關(guān)系的fetchType設(shè)置為lazy時(shí),會(huì)發(fā)生這種情況,以便僅在訪問(wèn)Set / List時(shí)才加載子實(shí)體。 假設(shè)我們有一個(gè)具有兩個(gè)關(guān)系的Customer實(shí)體:每個(gè)客戶的一組訂單和一組地址。
要加載所有客戶,我們可以發(fā)出以下JPQL語(yǔ)句,然后加載每個(gè)客戶的所有訂單:
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一起提供)將從數(shù)據(jù)庫(kù)中僅為兩個(gè)(!)客戶生成以下一系列SQL語(yǔ)句:
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=?如我們所見(jiàn),第一個(gè)查詢從表CustomerEntity中選擇所有客戶。 接下來(lái)的兩個(gè)選擇先提取,然后在第一個(gè)查詢中加載我們已加載的每個(gè)客戶的訂單。 當(dāng)我們有100個(gè)客戶而不是2個(gè)客戶時(shí),我們將獲得101個(gè)查詢。 一個(gè)初始查詢可加載所有客戶,然后針對(duì)100個(gè)客戶中的每個(gè)客戶,另外查詢一個(gè)訂單。 這就是為什么將此問(wèn)題稱為N + 1的原因。
解決此問(wèn)題的常見(jiàn)習(xí)慣是強(qiáng)制ORM生成內(nèi)部聯(lián)接查詢。 在JPQL中,可以通過(guò)使用JOIN FETCH子句來(lái)完成,如以下代碼片段所示:
entityManager.createQuery("SELECT c FROM CustomerEntity AS c JOIN FETCH c.orders AS o", CustomerEntity.class).getResultList();正如預(yù)期的那樣,ORM現(xiàn)在使用OrderEntity表生成一個(gè)內(nèi)部聯(lián)接,因此只需要一個(gè)SQL語(yǔ)句即可加載所有數(shù)據(jù):
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在您知道必須為每個(gè)客戶加載所有訂單的情況下,JOIN FETCH子句將SQL語(yǔ)句的數(shù)量從N + 1減少到1。這當(dāng)然具有缺點(diǎn),即您現(xiàn)在要轉(zhuǎn)移一個(gè)訂單的所有訂單。客戶一次又一次的客戶數(shù)據(jù)(由于查詢中的其他客戶列)。
JPA規(guī)范引入了2.1版,即所謂的NamedEntityGraphs。 此注釋使您可以描述JPQL查詢應(yīng)加載的圖形,而不是JOIN FETCH子句可以加載的圖形,從而為N + 1問(wèn)題提供了另一種解決方案。 下面的示例演示了我們的客戶實(shí)體的NamedEntityGraph,該實(shí)體僅加載客戶名稱及其訂單。 訂單在子圖中的orderGraph中有更詳細(xì)的描述。 在這里,我們看到我們只想加載訂單的字段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")})} )在通過(guò)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版本開(kāi)始支持@NamedEntityGraph注釋,并為上面顯示的JPQL查詢創(chuàng)建以下SQL語(yǔ)句:
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不會(huì)發(fā)出N + 1查詢,而是@NamedEntityGraph注釋強(qiáng)制Hibernate為每個(gè)左外部聯(lián)接加載訂單。 當(dāng)然,這與FETCH JOIN子句有微妙的區(qū)別,在子句中,Hibernate創(chuàng)建了一個(gè)內(nèi)部聯(lián)接。 與FETCH JOIN子句相反,左外部聯(lián)接還將加載不存在訂單的客戶,在FETCH JOIN子句中,我們僅加載至少具有一個(gè)訂單的客戶。
有趣的是,Hibernate加載的負(fù)載比表CustomerEntity和OrderEntity的指定屬性更多。 由于這與@NamedEntityGraph的規(guī)范(第3.7.4節(jié))相沖突,因此我為此創(chuàng)建了一個(gè)JIRA問(wèn)題 。
結(jié)論
我們已經(jīng)看到,在JPA 2.1中,我們?yōu)镹 + 1問(wèn)題提供了兩種解決方案:我們可以使用FETCH JOIN子句來(lái)急切地獲取@OneToMany關(guān)系,這將導(dǎo)致內(nèi)部聯(lián)接,或者我們可以使用@NamedEntityGraph功能我們指定通過(guò)左外部聯(lián)接加載哪個(gè)@OneToMany關(guān)系。
翻譯自: https://www.javacodegeeks.com/2014/07/using-namedentitygraph-to-load-jpa-entities-more-selectively-in-n1-scenarios.html
總結(jié)
以上是生活随笔為你收集整理的在N + 1场景中使用@NamedEntityGraph更有选择地加载JPA实体的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 条件概率公式 条件概率公式是什么
- 下一篇: Hive:使用Apache Hive查询