在N + 1场景中使用@NamedEntityGraph更有选择地加载JPA实体
N + 1問題是使用ORM解決方案時的常見問題。 當您將某些@OneToMany關系的fetchType設置為lazy時,會發生這種情況,以便僅在訪問Set / List時才加載子實體。 假設我們有一個具有兩個關系的Customer實體:每個客戶的一組訂單和一組地址。
要加載所有客戶,我們可以發出以下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
總結
以上是生活随笔為你收集整理的在N + 1场景中使用@NamedEntityGraph更有选择地加载JPA实体的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 条件概率公式 条件概率公式是什么
- 下一篇: 下头了是什么意思梗 下头了是啥意思梗