Hibernate n+1问题
Hibernate?中常會用到?set?,?bag?等集合表示?1?對多的關系,在獲取實體的時候就能根據關系將關聯的對象或者對象集取出,還可以設定?cacade?進行關聯更新和刪除。這不得不說?hibernate?的?orm?做得很好,很貼近?oo?的使用習慣了。
但是對數據庫訪問還是必須考慮性能問題的,在設定了?1?對多這種關系之后,?查詢就會出現傳說中的?n+1?問題。
?
出現的情況
一對多: 在一方,查找得到了 n 個對象,那么又需要將 n 個對象關聯的集合取出,于是本來的一條 sql 查詢變成了 n+1 條;多對一: 在多方,查詢得到了 m 個對象,那么也會將 m 個對象對應的 1 方的對象取出, 也變成了 m+1 ;?
?
其實這個問題在Hibernate in Action中已經有很多種解決辦法了。
下面總結一下。
?
需求這樣的,我有四張表(one,two,three,four)從one一直外鍵關聯到four。結構如下
現在在Session中得到One,并從One里一直取到Four里的內容。如果簡單的用Session.get來實現是這樣的。
One one = (One)session.get(One.class,new Integer(1));Iterator iterone = one.getTwos().iterator();while(iterone.hasNext()){Two two = (Two) iterone.next();Iterator itertwo = two.getThrees().iterator();while(itertwo.hasNext()){Three three = (Three) itertwo.next();three.getFours().size(); }}?
這樣我在Session關閉后返回的One里是從One到Four的信息都有的。
然而這樣做所導致的結果是生成大量的SQL查詢,這是一個典型的n+1 Selects問題。如果系統結構層次多,符合條件的記錄多,那么Hibernate為你生成的SQL查詢將是難以接受的。
對于這個例子生成的SQL是這樣的
?
對于這樣的問題,在沒有Hibernate以前我們一般都用jdbc來做,那樣的話我們其實用一個進行3次join的sql語句就可以實現,但是這樣解決也有問題,就是返回的ResultSet中的數據非常多,而且雜亂,其實是從one到four平行排列的。對于這樣的結果集我們要把它手動影射曾對象結構也是一個很復雜的操作。
幸好Hibernate3可以為我們做這些事情(我再一次被Hibernate的強大所震撼)。
上面的實現可以用Criteria來實現:
?
這里的重點是這句話
criteria.setFetchMode("twos",FetchMode.JOIN).setFetchMode("twos.threes",FetchMode.JOIN)
.setFetchMode("twos.threes.fours",FetchMode.JOIN)
.uniqueResult();
?
在用Criteria之前先設置FetchMode,應為Criteria是動態生成sql語句的,所以生成的sql就是一層層Join下去的。
setFetchMode(String,Mode)第一個參數是association path,用"."來表示路徑。這一點具體的例子很少,文檔也沒有寫清楚。我也是試了很久才試出來的。
就這個例子來所把因為取道第四層,所以要進行三次setFetchMode
第一次的路徑是twos,一位one中有two的Set。這個具體要更具hbm.xml的配置來定。
第二個路徑就是twos.threes
第三個就是twos.threes.fours
一次類推,一層層增加的。
這樣做法最終生成的SQL是這樣的:
?
雖然很長但是只有一條SQL語句。性能要好很多。Hibernate的強大之處是它會把返回的ResultSet自動影射到你的對象模型里面去。這就為我們省了很多事。
看來Hibernate真是一個耐人尋味的Framework啊。
源碼,沒什么東西。
?
?
Hibernate N+1 問題及解決辦法
? 1、?使用?fetch?抓取,?Hibernate?抓取策略分為單端代理和集合代理的抓取策略。
Hibernate?抓取策略?(?單端代理的抓取策略?)?:
???保持默認也就是如下?:
<many-to-one name="clazz" cascade="save-update" fetch="select" />?
????fetch="select"?就是另外發送一條?select?語句抓取當前對象關聯實體或者集合設置?fetch="join"??
<many-to-one name="clazz" cascade="save-update" fetch="join"/>?
Hibernate?會通過?select?語句使用外連接來加載器關聯實體活集合此時?lazy?會失效
???????Hibernate?抓取策略?(?集合代理的抓取策略?)?:
????????????保持默認(?fetch="select"?)也就是如下?:? ? ? ??
<set name="students" inverse="true"><key column="clazz"/><one-to-many class="com.june.hibernate.Student"/> </set>?
?????????????1)fetch="select"?會另外發出一條語句查詢集合
?????????????2)?設置?fetch="join"?采用外連接集合的?lazy?失效
?????????????3)?這只?fetch="subselect"?另外發出一條?select?語句抓取前面查詢到的所有的實體對象的關聯集合?fetch只對?HQL?查詢產生影響其他的則不會
?
? ? ???2、?使用?map?直接搜索需要的列
如:產品?product?和產品分類?product_category?兩張表,多對一關系。查詢產品列表時
select new Map(p.id as id, p.name as name, p.category.name as categoryName) from Product p?
?
? ? ? ?3、?延遲檢索策略
延遲檢索策略能避免多余加載應用程序不需要訪問的關聯對象,
hibernate3開始已經默認是lazy=true了;lazy=true時不會立刻查詢關聯對象,只有當需要關聯對象(訪問其屬性)時才會發生查詢動作。
?? ? ? ?4、?迫切左外連接檢索策略
迫切左外連接檢索策略則充分利用了SQL的外連接查詢功能,能夠減少select語句的數目。
可以在映射文件中定義連接抓取方式。
<set name=”orders” fetch=”join”><key column=”customer_id”><one-to-many class="com.hibernate.mappings.Order"/></set>?
或者使用HQL的LEFT OUTER JOIN.
或者在條件查詢中使用setFetchMode(FetchMode.JOIN)
Customer ctm = (Customer)session.createCriteria(Customer.class).setFetchMode(“Order”.JOIN).add(Restrictions.idEq(customer_id));?
轉載于:https://www.cnblogs.com/hwaggLee/p/4440263.html
總結
以上是生活随笔為你收集整理的Hibernate n+1问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c++ 对象指针参数和对象引用参数02
- 下一篇: 深度解析Objective-C笔试题