休眠事实:访存策略的重要性
在使用ORM工具時,每個人都承認數據庫設計和實體到表映射的重要性。 這些方面引起了很多關注,而諸如獲取策略之類的事情可能只是推遲了。
我認為,不應將實體獲取策略與實體映射設計分開,因為除非經過適當設計,否則它可能會影響整體應用程序性能。
在Hibernate和JPA如此流行之前,設計每個查詢需要付出大量的努力,因為您必須明確選擇要從中選擇的所有聯接以及所有您感興趣的列。足夠,DBA可以優化運行緩慢的查詢。
在JPA時代,JPA-QL或HQL查詢正在獲取實體及其一些關聯關系。 這使開發變得容易,因為它使我們免于手動選擇我們感興趣的所有表字段的麻煩,有時會自動生成聯接或其他查詢來滿足我們的需求。
這是一把雙刃劍。 一方面,您可以更快地交付功能,但如果自動生成的SQL查詢效率不高,則應用程序的整體性能可能會受到嚴重影響。
那么,實體獲取策略是什么?
當JPA加載實體時,它也會同時加載所有EAGER或“ join fetch”關聯。 只要打開持久性上下文,在LAZY關聯中導航也將通過其他已執行的查詢來獲取這些關聯。
默認情況下,將更容易地自動獲取JPA @ManyToOne和@OneToOne批注,而將@OneToMany和@ManyToMany關系視為LAZY。 這是默認策略,并且Hibernate不會神奇地優化對象檢索,它只會執行所指示的操作。
盡管小型項目不需要全面的實體獲取計劃,但中大型應用程序永遠都不應忽略它。
從一開始就計劃獲取策略,并在整個開發周期中進行調整,這并不是“過早的優化”,而這只是任何ORM設計的自然組成部分。
默認的獲取策略是您通過JPA映射定義的策略,而手動連接獲取則是在使用JPA-QL查詢時定義的策略。
我能給您的最佳建議是贊成手動獲取策略(在使用獲取操作符的JPA-QL查詢中定義)。 盡管一定要急切地獲取某些@ManyToOne或@OneToOne關聯,但在大多數情況下,并不是每個獲取操作都需要它們。
對于兒童協會,將它們標記為LAZY并僅在需要時才“聯接提取”它們總是比較安全的,因為它們可以輕松地生成帶有不需要聯接的大型SQL結果集。
將大多數關聯定義為LAZY要求我們使用“ join fetch” JPA-QL運算符,并且僅檢索滿足給定請求所需的關聯。 如果您忘記正確地“聯接獲取”,則在瀏覽惰性關聯時,持久性上下文將代表您運行查詢,這可能會產生“ N + 1”問題,或者可能是通過簡單聯接檢索到的其他SQL查詢首先。
作為一個具體的例子,讓我們從下圖開始:
產品實體關聯映射為:
@ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "company_id", nullable = false) private Company company;@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "product", optional = false) private WarehouseProductInfo warehouseProductInfo;@ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "importer_id") private Importer importer;@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "product", orphanRemoval = true) @OrderBy("index") private Set<Image> images = new LinkedHashSet<Image>();大多數關聯都標記為LAZY,因為每次加載產品時都不需要獲取所有關聯。 僅在顯示庫存信息時才需要倉庫。 導入程序僅在某些顯示中使用,我們將在必要時提取它。 圖像是惰性的,因為并非所有視圖都需要顯示這些圖像。
因為我們所有的觀點都需要它,所以只有公司熱切地被獲取,并且在我們的應用程序中,始終必須在給定公司的背景下考慮產品。
即使@ManyToOne默認使用EAGER fetch選項,最好還是顯式地設置默認的獲取策略(這會使代碼更具自描述性)。
用例1:通過id加載產品將生成以下SQL
SELECT product0_.id AS id1_7_1_,product0_.code AS code2_7_1_,product0_.company_id AS company_4_7_1_,product0_.importer_id AS importer5_7_1_,product0_.name AS name3_7_1_,company1_.id AS id1_1_0_,company1_.name AS name2_1_0_ FROM product product0_ INNER JOIN company company1_ ON product0_.company_id = company1_.id WHERE product0_.id = ?每次我們通過實體管理器加載時,都會使用默認的獲取策略,這意味著公司將與我們選擇的產品一起獲取。
用例2:通過JPA-QL查詢選擇產品(繞過持久性上下文第一級緩存)
entityManager.createQuery("select p " +"from Product p " +"where p.id = :productId", Product.class) .setParameter("productId", productId) .getSingleResult();這將執行以下SQL查詢:
SELECT product0_.id AS id1_7_,product0_.code AS code2_7_,product0_.company_id AS company_4_7_,product0_.importer_id AS importer5_7_,product0_.name AS name3_7_ FROM product product0_ WHERE product0_.id = ?因此,使用JPA-QL會覆蓋默認的獲取策略,但是如果我們要瀏覽惰性關聯,它仍然使我們容易受到攻擊。 如果Persistence Context關閉,則在訪問惰性關系時會出現LazyInitializationException,但如果未關閉,則會生成其他選擇查詢,這可能會影響應用程序性能。
用例3:選擇具有相關倉庫和進口商關聯的產品列表:
entityManager.createQuery("select p " +"from Product p " +"inner join fetch p.warehouseProductInfo " +"inner join fetch p.importer", Product.class) .getResultList();這將生成以下SQL:
SELECT product0_.id AS id1_7_0_,warehousep1_.id AS id1_11_1_,importer2_.id AS id1_3_2_,product0_.code AS code2_7_0_,product0_.company_id AS company_4_7_0_,product0_.importer_id AS importer5_7_0_,product0_.name AS name3_7_0_,warehousep1_.quantity AS quantity2_11_1_,importer2_.name AS name2_3_2_ FROM product product0_ INNER JOIN warehouseproductinfo warehousep1_ ON product0_.id = warehousep1_.id INNER JOIN importer importer2_ ON product0_.importer_id = importer2_.id在這里,您可以看到JPA-QL顯式提取策略將覆蓋默認策略。 因為我們尚未指定與Company的“ join fetch”,所以將忽略EAGER關聯。
用例4:在顯式加入獲取產品的同時選擇一系列圖像,即使所選實體不是我們要覆蓋其策略的實體,也將覆蓋默認策略:
entityManager.createQuery("select i " +"from Image i " +"inner join fetch i.product p " +"where p.id = :productId", Image.class) .setParameter("productId", productId) .getResultList();這將生成以下SQL:
SELECT image0_.id AS id1_2_0_,product1_.id AS id1_7_1_,image0_.index AS index2_2_0_,image0_.name AS name3_2_0_,image0_.product_id AS product_4_2_0_,product1_.code AS code2_7_1_,product1_.company_id AS company_4_7_1_,product1_.importer_id AS importer5_7_1_,product1_.name AS name3_7_1_ FROM image image0_ INNER JOIN product product1_ ON image0_.product_id = product1_.id WHERE product1_.id = ?我還需要添加一件事,這與WarehouseProductInfo的@oneToOne關系有關。 對于可選的@OnetoOne關聯,將忽略LAZY屬性,因為Hiberante必須知道它是否必須使用null或代理來填充您的Entity。 在我們的示例中,將其強制性是有意義的,因為無論如何每種產品都位于倉庫中。 在其他情況下,您可以簡單地使關聯成為單向的,并僅保留控制鏈接的部分(外鍵所在的部分)。
- 代碼可在GitHub上獲得 。
翻譯自: https://www.javacodegeeks.com/2013/11/hibernate-facts-the-importance-of-fetch-strategy.html
總結
以上是生活随笔為你收集整理的休眠事实:访存策略的重要性的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 罗马尼亚是哪个国家(是个怎样的国家)
- 下一篇: ADFLogger的SLF4J绑定–缺少