EAGER的获取是代码的味道
介紹
休眠獲取策略確實可以使幾乎沒有爬網的應用程序和響應速度很快的應用程序有所不同。 在這篇文章中,我將解釋為什么您應該選擇基于查詢的獲取而不是全局獲取計劃。
取得101
 Hibernate定義了四種關聯檢索策略 : 
| 加入 | 原始SELECT語句中的關聯是OUTER JOINED | 
| 選擇 | 附加的SELECT語句用于檢索關聯的實體(實體) | 
| 子選擇 | 附加的SELECT語句用于檢索整個關聯的集合。 此模式適用于許多關聯 | 
| 批量 | 其他數量的SELECT語句用于檢索整個關聯的集合。 每個其他SELECT都會檢索固定數量的關聯實體。 此模式適用于許多關聯 | 
 這些獲取策略可能適用于以下情況: 
- 關聯總是與其所有者一起初始化(例如EAGER FetchType)
- 導航未初始化的關聯(例如LAZY FetchType),因此必須使用輔助SELECT檢索關聯
Hibernate映射獲取信息形成了全局獲取計劃 。 在查詢時,我們可以覆蓋全局獲取計劃,但僅適用于LAZY關聯 。 為此,我們可以使用訪存HQL / JPQL / Criteria指令。 EAGER關聯不能被覆蓋,因此將您的應用程序與全局獲取計劃綁定在一起。
Hibernate 3承認LAZY應該是默認的關聯獲取策略:
默認情況下,Hibernate3對集合使用延遲選擇獲取,對單值關聯使用延遲代理獲取。 對于大多數應用程序中的大多數關聯而言,這些默認設置有意義。
在注意到與Hibernate 2默認渴望獲取有關的許多性能問題后,做出了此決定。 不幸的是,JPA采取了另一種方法,并決定對許多關聯為LAZY,而渴望獲得一對一的關系。
| @OneTMany | 懶 | 
| @多多多 | 懶 | 
| @多多 | 急于 | 
| @OneToOne | 急于 | 
EAGER獲取不一致
盡管將關聯標記為EAGER(將獲取職責委托給Hibernate)可能很方便,但還是建議使用基于查詢的獲取計劃。
始終會獲取EAGER關聯,并且獲取策略在所有查詢技術之間均不一致。
接下來,我將演示EAGER的獲取對于所有Hibernate查詢變量的行為。 我將重用我先前在獲取策略文章中介紹的實體模型:
產品實體具有以下關聯:
@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>();公司協會被標記為EAGER,并且Hibernate將始終采用獲取策略來對其及其所有者實體進行初始化。
持久性上下文加載
首先,我們將使用Persistence Context API加載實體:
Product product = entityManager.find(Product.class, productId);生成以下SQL SELECT語句:
Query:{[ select product0_.id as id1_18_1_, product0_.code as code2_18_1_, product0_.company_id as company_6_18_1_, product0_.importer_id as importer7_18_1_, product0_.name as name3_18_1_, product0_.quantity as quantity4_18_1_, product0_.version as version5_18_1_, company1_.id as id1_6_0_, company1_.name as name2_6_0_ from Product product0_ inner join Company company1_ on product0_.company_id=company1_.id where product0_.id=?][1]使用內部聯接檢索了EAGER公司關聯。 對于M個這樣的關聯,所有者實體表將被連接M次。
每個額外的連接會增加整體查詢的復雜性和執行時間。 如果我們甚至在所有可能的業務場景中都沒有使用所有這些關聯,那么我們就付出了額外的性能損失,卻一無所獲。
使用JPQL和條件進行獲取
Product product = entityManager.createQuery("select p " +"from Product p " +"where p.id = :productId", Product.class).setParameter("productId", productId).getSingleResult();或搭配
CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<Product> cq = cb.createQuery(Product.class); Root<Product> productRoot = cq.from(Product.class); cq.where(cb.equal(productRoot.get("id"), productId)); Product product = entityManager.createQuery(cq).getSingleResult();生成以下SQL SELECT語句:
Query:{[ select product0_.id as id1_18_, product0_.code as code2_18_, product0_.company_id as company_6_18_, product0_.importer_id as importer7_18_, product0_.name as name3_18_, product0_.quantity as quantity4_18_, product0_.version as version5_18_ from Product product0_ where product0_.id=?][1]} Query:{[ select company0_.id as id1_6_0_, company0_.name as name2_6_0_ from Company company0_ where company0_.id=?][1]}JPQL和Criteria查詢均默認選擇獲取,因此將為每個EAGER關聯發布輔助選擇。 關聯數越大,單個SELECTS越多,對我們應用程序性能的影響就越大。
休眠標準API
JPA 2.0添加了對Criteria查詢的支持,而Hibernate長期以來一直提供特定的動態查詢實現 。
如果EntityManager實現委托方法調用舊版Session API,則JPA Criteria實現是從頭開始編寫的。 這就是為什么Hibernate和JPA Criteria API在類似的查詢方案中表現不同的原因。
前面的示例Hibernate Criteria等效項如下所示:
Product product = (Product) session.createCriteria(Product.class).add(Restrictions.eq("id", productId)).uniqueResult();關聯的SQL SELECT是:
Query:{[ select this_.id as id1_3_1_, this_.code as code2_3_1_, this_.company_id as company_6_3_1_, this_.importer_id as importer7_3_1_, this_.name as name3_3_1_, this_.quantity as quantity4_3_1_, this_.version as version5_3_1_, hibernatea2_.id as id1_0_0_, hibernatea2_.name as name2_0_0_ from Product this_ inner join Company hibernatea2_ on this_.company_id=hibernatea2_.id where this_.id=?][1]}此查詢使用連接抓取策略,而不是選擇抓取,通過JPQL / HQL和標準的API使用。
休眠條件和多個EAGER集合
讓我們看看將圖像收集獲取策略設置為EAGER時會發生什么:
@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "product", orphanRemoval = true) @OrderBy("index") private Set<Image> images = new LinkedHashSet<Image>();將生成以下SQL:
Query:{[ select this_.id as id1_3_2_, this_.code as code2_3_2_, this_.company_id as company_6_3_2_, this_.importer_id as importer7_3_2_, this_.name as name3_3_2_, this_.quantity as quantity4_3_2_, this_.version as version5_3_2_, hibernatea2_.id as id1_0_0_, hibernatea2_.name as name2_0_0_, images3_.product_id as product_4_3_4_, images3_.id as id1_1_4_, images3_.id as id1_1_1_, images3_.index as index2_1_1_, images3_.name as name3_1_1_, images3_.product_id as product_4_1_1_ from Product this_ inner join Company hibernatea2_ on this_.company_id=hibernatea2_.id left outer join Image images3_ on this_.id=images3_.product_id where this_.id=? order by images3_.index][1]}休眠條件不會自動將父實體列表分組。 由于存在一對多子表JOIN,因此對于每個子實體,我們將獲得一個新的父實體對象引用(在我們當前的持久性上下文中,它們均指向同一對象):
product.setName("TV"); product.setCompany(company);Image frontImage = new Image(); frontImage.setName("front image"); frontImage.setIndex(0);Image sideImage = new Image(); sideImage.setName("side image"); sideImage.setIndex(1);product.addImage(frontImage); product.addImage(sideImage);List products = session.createCriteria(Product.class).add(Restrictions.eq("id", productId)).list(); assertEquals(2, products.size()); assertSame(products.get(0), products.get(1));因為我們有兩個圖像實體,所以我們將獲得兩個Product實體引用,它們均指向同一一級緩存條目。
要解決此問題,我們需要指示休眠標準使用不同的根實體:
List products = session.createCriteria(Product.class).add(Restrictions.eq("id", productId)).setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY).list(); assertEquals(1, products.size());結論
EAGER的獲取策略是一種代碼味道。 大多數情況下,它是出于簡化的目的而使用,而不考慮長期的性能損失。 提取策略絕不應成為實體映射的責任。 每個業務用例都有不同的實體負載要求,因此,應將獲取策略委托給每個單獨的查詢。
全局提取計劃應僅定義LAZY關聯,這些關聯是在每個查詢的基礎上提取的。 與始終檢查生成的查詢策略結合使用,基于查詢的獲取計劃可以提高應用程序性能并降低維護成本。
- Hibernate和JPA可用的代碼。
翻譯自: https://www.javacodegeeks.com/2014/12/eager-fetching-is-a-code-smell.html
總結
以上是生活随笔為你收集整理的EAGER的获取是代码的味道的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: io域名备案(国内io备案)
- 下一篇: (备案合同密码是什么)
