JPA 2 | 获取联接以及我们是否应该使用它们
介紹
最近,我一直在與JPA 2中的FETCH JOINS一起使用,以期從數(shù)據(jù)庫中急切地獲取數(shù)據(jù),并且我學(xué)到了很多關(guān)于為什么在日常操作中應(yīng)避免使用Fetch Joins的知識(shí)。
今天的博客文章談?wù)摿宋以贔etch上的經(jīng)歷和學(xué)習(xí)(主要基于當(dāng)我在查詢中有很多FETCH JOINS時(shí)獲得的評(píng)論)。
問題陳述
在我們的項(xiàng)目中,有一種情況是從定義了許多集合值關(guān)聯(lián)的數(shù)據(jù)庫(OneToMany,ManyToMany,也稱為ToMany關(guān)聯(lián))中獲取實(shí)體。 這是實(shí)體外觀的概念圖(為清楚起見,省略了getter和setter)。 這是實(shí)體的極其簡化的示例。 在實(shí)際情況下,我們大約有11個(gè)關(guān)聯(lián)。
public class ItemRecord {@Idprivate String itemId;@Column(name="author")private String author;@OneToMany(cascade = CascadeType.ALL, mappedBy = "item")private List costs;@OneToMany(cascade = CascadeType.ALL, mappedBy = "item")private List notes;@OneToMany(cascade = CascadeType.ALL, mappedBy = "item")private List stats;}關(guān)于上述實(shí)體,有幾件事需要注意:
- 它具有3個(gè)收藏珍貴的協(xié)會(huì)。
- 所有這些關(guān)聯(lián)都被延遲獲取,因?yàn)镴PA中“集合有價(jià)值的關(guān)聯(lián)”的默認(rèn)獲取策略是“惰性”。
在我們的業(yè)務(wù)實(shí)現(xiàn)中,我們有一個(gè)轉(zhuǎn)換器,該轉(zhuǎn)換器將DAO層返回的值轉(zhuǎn)換為Business DTO。
因此,我們的業(yè)務(wù)方法的算法如下:
@TransactionAttribute public List searchItemRecords (SearchCriteria sc) {List ir = itemRecordDao.search(sc);List convertedData = recordConverter.convert(ir);return convertedData; }請(qǐng)注意,整個(gè)方法都在Transaction內(nèi)部運(yùn)行。
每當(dāng)我們從數(shù)據(jù)庫中獲取數(shù)據(jù)時(shí),就不會(huì)急切地獲取與成本,統(tǒng)計(jì)信息等相關(guān)的數(shù)據(jù)。 但是我們的ItemInformation DTO期望所有數(shù)據(jù)。 因此,當(dāng)首次調(diào)用getCosts或getStatistics時(shí),持久性提供程序(在本例中為Hibernate)向數(shù)據(jù)庫激發(fā)查詢以獲取指定的數(shù)據(jù)。 這為我們創(chuàng)建了N + 1個(gè)選擇查詢問題。 如果您不熟悉N + 1選擇或需要刷新,可以在DZone上查看此文章 。
解
我們大多數(shù)人,包括我在內(nèi),都會(huì)選擇N + 1選擇問題的最快,最簡單的解決方案,即使用Fetch Join。 在Internet上發(fā)布的不同博客/文章中也有很多建議。
因此,我也采用了相同的方法。 就我而言,這至少是一種糟糕的方法。
首先讓我們看看如何使用FETCH JOIN。
使用Fetch Join之前的查詢?nèi)缦?#xff1a;
SELECT item FROM ItemRecord item WHERE author=:author;請(qǐng)注意,查詢采用JPA形式。
該查詢未獲取集合值。 結(jié)果,在翻譯器中,當(dāng)我們對(duì)每個(gè)ItemRecord執(zhí)行g(shù)etCosts時(shí),將觸發(fā)類似于以下的查詢:
SELECT cost FROM Cost where itemId = :itemId因此,如果我們有3個(gè)ItemRecords,則激發(fā)到數(shù)據(jù)庫的SELECT查詢總數(shù)為:
- 1用于獲取所有ItemRecords
- 3個(gè)用于獲取每個(gè)ItemRecord的成本
- 3用于獲取每個(gè)ItemRecord的注釋
- 3個(gè)用于獲取每個(gè)ItemRecord的統(tǒng)計(jì)信息
即(3乘3)+ 1
當(dāng)將其轉(zhuǎn)換為N乘以M +1時(shí),
哪里,
- N是找到的主要實(shí)體記錄的數(shù)量
- M是主要實(shí)體中Collection值關(guān)聯(lián)的數(shù)量
- 1個(gè)查詢,用于獲取所有主要實(shí)體
在實(shí)際情況下,我們有11個(gè)關(guān)聯(lián)。 因此,對(duì)于每個(gè)主要ItemRecord實(shí)體,我們將觸發(fā)11個(gè)SELECT查詢。 激發(fā)的查詢數(shù)量乘以找到的每個(gè)ItemRecord。
使得集合值關(guān)聯(lián)成為EAGER是不可行的,因?yàn)樵趯?shí)體上運(yùn)行的許多其他查詢僅需要選定的數(shù)據(jù)。
這不是最佳解決方案。 必須做一些事情,很多互聯(lián)網(wǎng)文章建議使用FETCH JOINS,因?yàn)樗鼈冏钊菀讓?shí)現(xiàn)并解決了(N Time M +1)個(gè)查詢問題。
因此,我決定在查詢中使用FETCH Joins來針對(duì)給定場景急切地獲取所有數(shù)據(jù)。
該查詢類似于:
SELECT item FROM ItemRecordJOIN FETCH item.costs, JOIN FETCH item.notes, JOIN FETCHitem.stats;跨欄1
我很快意識(shí)到,該查詢?cè)谖业那闆r下將不起作用,因?yàn)槲铱梢該碛幸粋€(gè)沒有任何與之關(guān)聯(lián)的統(tǒng)計(jì)信息的ItemRecord,在這種情況下,上述查詢不會(huì)向我返回那個(gè)ItemRecord(由于ORM的工作方式)因此我將獲得不完整的數(shù)據(jù)。
因此,我接下來轉(zhuǎn)到LEFT JOIN FETCH,即使某些關(guān)聯(lián)關(guān)系為空,它也將給我ItemRecord實(shí)體返回。 查詢?nèi)缦滤?#xff1a;
SELECT item FROM ItemRecordLEFT JOIN FETCH item.costs, LEFT JOIN FETCH item.notes, LEFT JOIN FETCH item.stats;跨欄2
當(dāng)我運(yùn)行單元測試以測試上述查詢時(shí),出現(xiàn)了異常:
javax.persistence.PersistenceException: org.hibernate.HibernateException: cannot simultaneously fetch multiple bags問題是什么?
問題是我在實(shí)體中使用列表作為集合類型。 使用列表會(huì)混淆JPA /休眠。 這篇文章很好地記錄了這種困惑。
為了解決此問題,我選擇使用Set而不是List,主要是因?yàn)樗巧鲜霾┛臀恼绿峁┑娜N解決方案中最簡單的方法,并且也很有意義(至少在我實(shí)現(xiàn)時(shí))。
因此,我將Entity更改為Set而不是List,并且修改后的實(shí)體如下所示:
public class ItemRecord {@Idprivate String itemId;@Column(name="author")private String author;@OneToMany(cascade = CascadeType.ALL, mappedBy = "item")private Set costs;@OneToMany(cascade = CascadeType.ALL, mappedBy = "item")private Set notes;@OneToMany(cascade = CascadeType.ALL, mappedBy = "item")private Set stats;}我再次運(yùn)行測試用例,并且測試查詢的測試用例成功。 耶皮 但是,我的另一個(gè)測試用例的測試用例失敗了。 看一下測試用例,我意識(shí)到我需要按照特定的順序輸入數(shù)據(jù),并且使用的是未排序的HashSet。 解決方案很簡單。 使用LinkedHashSet維護(hù)元素的順序。
使用LinkedHashSet可以解決問題,并且我的測試用例通過了。
我很高興,但我的幸福短暫。
跨欄3
我有另一個(gè)測試用例,它預(yù)期給定ItemRecord的3個(gè)成本對(duì)象。 一旦我轉(zhuǎn)到“設(shè)置實(shí)現(xiàn)”,測試就開始失敗。 原來我的哈希碼不正確,并且我的“成本實(shí)體”的實(shí)現(xiàn)等于“實(shí)現(xiàn)”,“成本實(shí)體”為兩個(gè)不同的實(shí)體返回相同的哈希碼,結(jié)果,由于Set不允許重復(fù)值,因此僅持久保留了一個(gè)實(shí)體。
因此,我接下來要做的就是為我的所有實(shí)體使用適當(dāng)?shù)腍ashCode和Equals實(shí)現(xiàn)。
最終關(guān)卡
最終,當(dāng)我所有的測試用例開始通過時(shí),我進(jìn)行了代碼審查,并將其發(fā)送給團(tuán)隊(duì)。
第一個(gè)嚇到我的是我的技術(shù)主管。 :)
他只是生氣地看著FETCH JOINS。 原因? 原因是LEFT FETCH JOINs返回所有數(shù)據(jù)的笛卡爾積。 鑒于我們生產(chǎn)中的數(shù)據(jù)量很大,甚至在ItemRecord上支持多個(gè)選擇也將成為一場噩夢。 整個(gè)問題可以在此博客文章中輕松理解。
因此,我試圖解決性能問題,事實(shí)證明我實(shí)際上是在制造更大的性能問題。 :)
刪除了轉(zhuǎn)移到FETCH JOIN的整個(gè)解決方案,因此決定進(jìn)一步研究為什么我們需要UI上的全部數(shù)據(jù),以及為什么不能將獲取數(shù)據(jù)分成較小的專用事務(wù)。
摘要:
使用Fetch Joins的整個(gè)過程使我很好地了解了Joins的總體工作原理以及使用它們時(shí)的期望。
希望您喜歡這篇博客文章。 如果您想繼續(xù)閱讀有趣的帖子,可以關(guān)注我的博客。
翻譯自: https://www.javacodegeeks.com/2013/07/jpa-2-fetch-joins-and-whether-we-should-use-them.html
總結(jié)
以上是生活随笔為你收集整理的JPA 2 | 获取联接以及我们是否应该使用它们的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring @Bean和Propert
- 下一篇: 铁岭是几线城市(辽宁第3大市比沈阳还大是