標籤:hibernate orm fetch
處理關聯關係是ORM中一常見操作,特別是在查詢的時候,經常要在查詢某個實體的時候要把它實體關聯屬性也查詢出來,例如查詢使用者時級聯查詢角色資訊,還有可能角色及聯查詢許可權資訊。在hibernate中實現這個目的有很多總方式:
1.配置OpenSessionInViewFilter,讓Session在View層中儲存開啟狀態,可以隨時使用,這看起來是個一勞永逸的辦法,但其也帶來了一些問題, 至於會有什麼樣的問題百度會給你答案。
2.在映射實體時把關聯屬性設定lazy="false",表示該關聯關係不進行懶載入,這樣該實體關聯屬性就會一起被查詢出來,這種方式有時候是可行的,但最大的問題就是不靈活,一但配置lazy="false",有時候不想該實體關聯屬性一起被查詢出來就無能為力了
3.在hql中提供了一個"fetch"關鍵字,其字面意義就是"抓取",利用它可以靈活地抓取我們想要查詢實體關聯屬性。例如:查詢使用者抓取角色:FROM User u LEFT JOIN FETCH u.roles rs WHERE...,如果角色還要抓取限制,則:FROM User u LEFT JOIN FETCH u.roles rs LEFT JOIN FETCH rs.privileges ps WHERE...
FETCH使用起來的確很靈活,但在最近的一次使用中卻碰見了一個問題,就是在得到的結果集中可能出現多條相同的記錄,這是我們所不想要的
舉個例子:
/** 論壇主題 **/public class Theme {/** 資料庫ID **/private Integer id;/** 標題 **/private String title;/** 主題內容 **/private String content;/** 回複 **/private Set<Reply> replies = new HashSet<Reply>();//getter...//setter...}
/** 主題回複 **/public class Reply {/** 資料庫id **/private Integer id;/** 回複內容 **/private String content;/** 回複所屬主題 **/private Theme theme;//getter...//setter...}
為了簡單起見,只是列出了樣本屬性,getter與setter方法也省略掉了。現在當我們查詢Theme的時候要抓取出Reply,這時hql語句為:FROM Theme t LEFT JOIN FETCH t.replies rs WHERE t.id=?,執行查詢後得到Query對象,這時你如果調用的是uniqueResult()方法你將得到正確的結果,但是當你調用的是list()方法,你會發現這個結果清單中很可能出現了多條重複的記錄,確切的說是該Theme有多少個Reply就會有多少條重複的記錄,這時你肯定會說調用uniqueResult()方法不就完了嗎?在這種本來就只有一條記錄的情況肯定是沒有問題的,但有時候我們想返回的就是多條記錄呢?比如,根據Theme的title屬性進行模糊比對(不考慮全文索引)的時候,這時hql語句為:FROM Theme t LEFT JOIN FETCH t.replies rs WHERE t.title like ?,我們要返回的就是一個List列表,這時只能調用Query.list()方法,這樣勢必會出現多條重複記錄。那麼如何解決呢?
1.加上DISTINCT關鍵字,hql變為:FROM DISTINCT Theme t LEFT JOIN FETCH t.replies rs WHERE t.title like ?,這樣一般情況下 能返回確定的結果,但有時候也不行。例如,在Oracle中當有欄位為clob,blob類型時就行不通過了,因為資料根本不知道對於clob,blob類型 的資料到底是不是相等,其它資料庫就沒有試過了,我估計也不行。所以DISTINCT關鍵字也不是萬能的。
2.在返回含有相同記錄的List中,手工進行排除掉相同的記錄,但這會增加額外的工作量,大家肯定也不願幹。
3.在調用list()方法之前,調用query.setFirstIndex(0).setMaxResult(Integer.MAX_VALUE);這樣一個分頁語句肯定是擷取出了全部的記錄 這樣雖然能擷取出正確的結果,但你會發現和沒有設定分頁執行的SQL語句是一樣的,這就說明在這種情況下hibernate是先擷取出了全部符合 條件的記錄然後再進行分頁的。當你不需要分頁的情況下執行是沒有問題的,如果你又想分頁又想級聯抓取就會擷取出不必要的記錄,因為 它是在記憶體中分頁的。而且這樣hibernate還會報一個警告:WARNING: firstResult/maxResults specified with collection fetch; applying in memory! 大致意思說的就是在記憶體中進行分頁,這樣肯定有效能方面的問題,這個問題可以參看:點擊開啟連結
在相比之下,個人覺得還是第3種解決方案是最合適的(如果還有更好的解決方案請不吝賜教),因為只要不進行分頁都是可以滿足要求的,如果要進行分頁只能不進行級聯抓取了,要擷取實體關聯屬性另外寫查詢語句。