標籤:
在上一篇部落格中,介紹了Hibernate的緩衝機制。合理的配置緩衝,可以極大程度上最佳化Hibernate的效能。這篇部落格,介紹另外一個調優方式:抓取策略。
一、什麼是抓取策略
抓取策略(fetching strategy):當應用程式需要在關聯關係間進行導航的時候,Hibernate如何擷取關聯對象的策略。抓取策略可以在O / R映射的中繼資料中聲明,也可以在特定的HQL或條件查詢(Criteria Query)中重載聲明。
二、Hibernate的抓取策略
在Hibernate中,主要包括四種抓取策略:
2.1,串連抓取(Join fetching)
Hibernate通過在select語句中使用join等關鍵字,來獲得對象的關聯執行個體或者關聯集合
2.2,查詢抓取(Select fetching)
Hibernate通過發送另外一條select語句抓取當前對象的關聯實體或集合。除非是顯示的指定lazy=“false”禁止延遲抓取,否則只有當你真正訪問關聯關係的時候,才會執行第二條select語句。
2.3,子查詢抓取(Subselect fetching)
Hibernate通過另外發送一條SELECT 語句抓取在前面查詢到(或者抓取到)的所有實體物件的關聯集合。除非你顯式的指定lazy="false" 禁止延遲抓取(lazy fetching),否則只有當你真正訪問關聯關係的時候,才會執行第二條select語句。
2.4,批量抓取(Batch fetching)
是Hibernate對查詢抓取的最佳化方案, 通過指定一個主鍵或外鍵列表,Hibernate使用單條SELECT語句擷取一批對象執行個體或集合。
二、詳解Hibernate的抓取策略
首先,在Hibernate.cfg.xml中配置抓取策略的資料量(非必須,比如批次更新,可以在用到批次更新的時候配置):
<span style="font-family:KaiTi_GB2312;font-size:18px;"> <!-- 批次更新配置 --><property name="hibernate.jdbc.batch_size">30</property> <!-- 批量抓取參數的配置 --><property name="hibernate.jdbc.fetch_size">50</property></span>
2.1,串連抓取
<span style="font-family:KaiTi_GB2312;font-size:18px;"><many-to-one name="classes" column="classesid" fetch="join" not-null="true"/></span>
fetch="join",hibernate會通過select語句使用外串連來載入其關聯實體或集合,不過,此時的lazy失效
測試方法代碼如查詢抓取中的testFetch1()。
控制台列印:
Hibernate: select student0_.id as id0_0_, classes1_.id as id1_1_, student0_.name as name0_0_, student0_.classesid as classesid0_0_, classes1_.name as name1_1_from t_student student0_ inner join t_classes classes1_ on student0_.classesid=classes1_.idwhere student0_.id=1
student.name=班級0的學生0
classes.name=班級0
2.2,查詢抓取
<span style="font-family:KaiTi_GB2312;font-size:18px;"><many-to-one name="classes" column="classesid" fetch="select"/></span>
fetch="select",另外發送一條select語句抓取當前對象關聯實體或集合
<span style="font-family:KaiTi_GB2312;font-size:18px;">public void testFetch1() {Session session = null;try {session = HibernateUtils.getSession();session.beginTransaction();Student student = (Student)session.load(Student.class, 1);System.out.println("student.name=" + student.getName());Classes classes = student.getClasses();System.out.println("classes.name=" + classes.getName());session.getTransaction().commit();}catch(Exception e) {e.printStackTrace();session.getTransaction().rollback();}finally {HibernateUtils.closeSession(session);}}</span>
控制台列印:
Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.classesid as classesid0_0_ from t_student student0_where student0_.id=?
student.name=班級0的學生0
Hibernate: select classes0_.id as id1_0_, classes0_.name as name1_0_from t_classes classes0_ where classes0_.id=?
classes.name=班級0
預設的啟用了消極式載入,所以,僅僅當執行Classes classes=student.getClasses()時,才發出了另一條select語句。
2.3,子查詢抓取
<span style="font-family:KaiTi_GB2312;font-size:18px;"><set name="students" order-by="id" inverse="true" cascade="all" fetch="subselect"><key column="classesid"/><one-to-many class="com.bjpowernode.hibernate.Student"/></set></span>
fetch="subselect",另外發送一條select語句抓取在前面查詢到的所有實體物件的關聯集合
<span style="font-family:KaiTi_GB2312;font-size:18px;">public void testFetch1() {Session session = null;try {session = HibernateUtils.getSession();session.beginTransaction();List classesList = session.createQuery("select c from Classes c where c.id in(1, 2, 3)").list();for (Iterator iter1 = classesList.iterator(); iter1.hasNext();) {Classes classes = (Classes) iter1.next();System.out.println(classes.getName());for (Iterator iter = classes.getStudents().iterator(); iter.hasNext();) {Student student = (Student) iter.next();System.out.println(student.getName());}}session.getTransaction().commit();} catch (Exception e) {e.printStackTrace();session.getTransaction().rollback();} finally {HibernateUtils.closeSession(session);}}</span>控制台列印:
Hibernate: select classes0_.id as id1_, classes0_.name as name1_ from t_classes classes0_ where classes0_.id in (1 , 2 , 3)
班級0
Hibernate: select students0_.classesid as classesid1_, students0_.id as id1_, students0_.id as id0_0_, students0_.name as name0_0_, students0_.classesid as classesid0_0_from t_student students0_ where students0_.classesid in (select classes0_.id from t_classes classes0_where classes0_.id in (1 , 2 , 3)) order by students0_.id
班級0的學生0
班級0的學生1
班級0的學生2
班級0的學生3
班級0的學生4
班級0的學生5
班級0的學生6
班級0的學生7
班級0的學生8
班級0的學生9
班級1
班級1的學生0
班級1的學生1
班級1的學生2
班級1的學生3
班級1的學生4
班級1的學生5
班級1的學生6
班級1的學生7
班級1的學生8
班級1的學生9
班級2
班級2的學生0
班級2的學生1
班級2的學生2
班級2的學生3
班級2的學生4
班級2的學生5
班級2的學生6
班級2的學生7
班級2的學生8
班級2的學生9
2.4,批量抓取
單端代理的批量抓取:Hibernate可以充分有效使用批量抓取,也就是說,如果僅一個訪問代理(或集合),那麼Hibernate將不載入其他未執行個體化的代理。 批量抓取是延遲查詢抓取的最佳化方案,你可以在兩種批量抓取方案之間進行選擇:在類層級和集合層級。
如果:你在一個Session中載入了25個 Cat執行個體,每個Cat執行個體都擁有一個引用成員owner, 其指向Person,而Person類是代理,同時lazy="true"。 如果你必須遍曆整個cats集合,對每個元素調用getOwner()方法,Hibernate將會預設的執行25次SELECT查詢, 得到其owner的代理對象。這時,你可以通過在對應檔的Person屬性,顯式聲明batch-size,改變其行為:
<class name="Person" batch-size="10">...</class>隨之,Hibernate將只需要執行三次查詢,分別為10、10、 5。
執行個體分析:
<span style="font-family:KaiTi_GB2312;font-size:18px;"><class name="com.angel.hibernate.Classes" table="t_classes" batch-size="10"></span>
測試類別:
<span style="font-family:KaiTi_GB2312;font-size:18px;">public void testFetch1() {Session session = null;try {session = HibernateUtils.getSession();session.beginTransaction();List students = session.createQuery("select s from Student s where s.id in(:ids)").setParameterList("ids",new Object[] { 1, 11, 21, 31, 41, 51, 61, 71, 81,91 }).list();for (Iterator iter = students.iterator(); iter.hasNext();) {Student student = (Student) iter.next();System.out.println(student.getName());System.out.println(student.getClasses().getName());}session.getTransaction().commit();} catch (Exception e) {e.printStackTrace();session.getTransaction().rollback();} finally {HibernateUtils.closeSession(session);}}</span>控制台輸出結果:
Hibernate: select student0_.id as id0_, student0_.name as name0_, student0_.classesid as classesid0_ from t_student student0_ where student0_.id in (? , ? , ? , ? , ? , ? , ? , ? , ? , ?)
班級0的學生0
Hibernate: select classes0_.id as id1_0_, classes0_.name as name1_0_ from t_classes classes0_ where classes0_.id in (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
班級0
班級1的學生0
班級1
班級2的學生0
班級2
班級3的學生0
班級3
班級4的學生0
班級4
班級5的學生0
班級5
班級6的學生0
班級6
班級7的學生0
班級7
班級8的學生0
班級8
班級9的學生0
班級9
如果不配置批量抓取batch-size="10",那麼控制台列印的結果為(出現了N+1問題,所以,配置批量抓取,也可以解決N+1問題):
Hibernate: select student0_.id as id0_, student0_.name as name0_, student0_.classesid as classesid0_ from t_student student0_ where student0_.id in (? , ? , ? , ? , ? , ? , ? , ? , ? , ?)
班級0的學生0
Hibernate: select classes0_.id as id1_0_, classes0_.name as name1_0_ from t_classes classes0_ where classes0_.id=?
班級0
班級1的學生0
Hibernate: select classes0_.id as id1_0_, classes0_.name as name1_0_ from t_classes classes0_ where classes0_.id=?
班級1
班級2的學生0
Hibernate: select classes0_.id as id1_0_, classes0_.name as name1_0_ from t_classes classes0_ where classes0_.id=?
班級2
班級3的學生0
Hibernate: select classes0_.id as id1_0_, classes0_.name as name1_0_ from t_classes classes0_ where classes0_.id=?
班級3
班級4的學生0
Hibernate: select classes0_.id as id1_0_, classes0_.name as name1_0_ from t_classes classes0_ where classes0_.id=?
班級4
班級5的學生0
Hibernate: select classes0_.id as id1_0_, classes0_.name as name1_0_ from t_classes classes0_ where classes0_.id=?
班級5
班級6的學生0
Hibernate: select classes0_.id as id1_0_, classes0_.name as name1_0_ from t_classes classes0_ where classes0_.id=?
班級6
班級7的學生0
Hibernate: select classes0_.id as id1_0_, classes0_.name as name1_0_ from t_classes classes0_ where classes0_.id=?
班級7
班級8的學生0
Hibernate: select classes0_.id as id1_0_, classes0_.name as name1_0_ from t_classes classes0_ where classes0_.id=?
班級8
班級9的學生0
Hibernate: select classes0_.id as id1_0_, classes0_.name as name1_0_ from t_classes classes0_ where classes0_.id=?
班級9
三、總結
抓取策略和緩衝機制一樣,都是Hibernate調優的重要手段:我們通過Hibernate的抓取策略,減少發出sql的次數,將其資料存入緩衝,再次減少開合資料的次數。但並不是每一個應用程式都必須用上這些東西。具體的使用,還需要根據業務的需求,酌情而定。下面一篇部落格,將介紹鎖的概念:悲觀鎖和樂觀鎖
【Hibernate 8】Hibernate的調優方法:抓取策略