Hibernate大量操作(二)

來源:互聯網
上載者:User

標籤:jdk   parameter   lazy   sort排序   相同   sam   機制   object   ret   

Hibernate提供了一系列的查詢介面,這些介面在實現上又有所不同。這裡對Hibernate中的查詢介面進行一個小結。

我們首先來看一下session載入實體物件的過程:Session在調用資料庫查詢前,首先會在緩衝中進行查詢。如果在內部緩衝中通過實體類型和id進行尋找並命中,資料狀態合法,則直接返回。如果內部緩衝中未發現有效資料,則查詢第二級緩衝,如果第二級快取命中,則返回。如在第二級緩衝中沒有命中,則發起資料庫查詢操作(Select SQL),根據映射配置和Select SQL得到的ResultSet,建立對應的資料對象。將其資料對象納入當前Session實體管理容器(一級緩衝)。執行Interceptor.onLoad方法(如果有對應的Interceptor),將資料對象納入二級緩衝。如果資料對象實現了LifeCycle介面,則調用資料對象的onLoad方法。返回資料對象。

利用緩衝可以使查詢效能得到了大幅提高,但Hibernate的實現中並非所有的介面都利用了緩衝機制。Session2的find/iterator方法均可根據指定條件查詢並返回符合查詢條件的實體物件集。Hibernate3中的查詢介面Query中的list/iterate方法也實現了相同的功能(Hibernate3.x版本中淘汰了find()方法,使用Query查詢介面)。從實現機制上,這兩個介面沒有本質差別。從緩衝方面上,find/ list方法通過一條select sql實現查詢操作,而iterate則執行1+ N次查詢, 首先執行select sql擷取滿足條件的id,再根據每個id擷取對應的記錄。

但是,find方法(hibernate2)/Query的list(hibernate3)實際上不利用緩衝,它對緩衝唯寫不讀。find方法將執行Select從資料庫中獲得所有合格記錄並構造相應的實體物件,實體物件構建完畢之後,就將其納入緩衝中。

而iterate方法(hibernate2)/Query的iterate(hibernate3)則充分利用了緩衝中的資料,iterate方法執行時,它首先會執行一條Select SQL以獲得所有滿足查詢條件的資料id,然後再從內部緩衝中根據id尋找對應的實體物件(類似Session.load方法),如果緩衝中存在對應的資料,則直接以此資料對象作為查詢結果,如果沒有找到,則再執行相應的Select語句從資料庫中獲得對應的記錄,並構建相應的資料對象作為查詢結果,並且該結果也被納入緩衝中。這樣,如果查詢資料唯讀或者讀取相對較為頻繁,通過這種機制可以大大減少效能上的損耗。

另一個方面,我們知道內部緩衝容量並沒有限制,在查詢過程中,大量的資料對象被納入內部緩衝中,從而帶來了相應的記憶體消耗。為了控制內部緩衝的大小,可以手動清除hibernate的緩衝。也可結合iterator方法和evict方法逐條對資料對象進行處理,將記憶體消耗在可接受的範圍之內。如下:

String hql = "from XXX";

Query query = session.createQuery(hql);

Iterator iter = query.iterate();

while(iter.hasNext()){

    Object obj = iter.next();

    session.evict(obj);

}

所以資料量過大時, 應該避免使用find(list),推薦使用iterate(iterate)。實際應用開發中,對於大批量資料處理,還是推薦採用SQL或預存程序實現,以獲得較高效能。

 

使用基於遊標的資料遍曆操作也是一個好的查詢方法,通過遊標,可以逐條擷取資料,從而使得記憶體處於較為穩定的使用狀態。如下:

String hql = "from XXX";

Query query = session.createQuery(hql);

ScrollableResults scres = query.scroll();

while(scRes.next()) {

   Object obj = scRes.get(0);

    //。。。

}

 

此外,Hibernate還提供了一種從Criteria對象中增加查詢條件的實現。Criteria Query通過物件導向化的設計,將資料查詢條件封裝為一個對象。簡單來講,Criteria Query可以看作是傳統SQL的對象化表示,如:

Criteria criteria = session.createCriteria(TUser.class);

criteria.add(Expression.eq("name","Erica"));

criteria.add(Expression.eq("sex",new Integer(1)));

criteria.addOrder(Order.asc("name")); //增加排序

這裡的criteria 執行個體實際上是SQL “Select * from t_user where name=’Erica’ and sex=1”的封裝。Hibernate 在運行期會根據Criteria 中指定的查詢條件(也就是上面代碼中通過criteria.add方法添加的查詢運算式)產生相應的SQL語句。

 

對於大量資料的查詢,可以利用分頁來控制每次查詢返回記錄的數量。在Hibernate中,通過Criteria(或者Query)介面的setFirstResult, setMaxResults 方法來限制一次查詢返回的記錄範圍。下面是一個在Spring模板中使用Query介面分頁的樣本。

public <T> List<T> findByPage(final String hql, final Object[] values, final int offset, final int pageSize) {

        List<T> list = getHibernateTemplate().executeFind(// 通過一個HibernateCallback對象來執行查詢

                new HibernateCallback() {

                    // 實現HibernateCallback介面必須實現的方法

                    public Object doInHibernate(Session session) throws HibernateException, SQLException {

                        // 執行Hibernate分頁查詢

                        Query query = session.createQuery(hql);

                        if (values != null) {

                            // 為hql語句傳入參數

                            for (int i = 0; i < values.length; i++) {

                                query.setParameter(i, values[i]);

                            }

                        }

                        List<T> result = query.setFirstResult(offset).setMaxResults(pageSize).list();

                        return result;

                    }

                });

        return list;

    }

分頁通常與排序相關,Hibernate中主要有兩種排序方式:1)Sort、2)order-by。

sort :Hibernate中提供了可排序Set,它實現了java.util.SortedSet .  在set元素中可配置sort屬性(sort=‘natural‘, 指定採用Java預設排序機制,通過調用資料類型的compareTo方法。可以自訂java.util.Comparator介面的實現實現自訂的排序演算法,來作為sort的屬性值。Map類型與Set基本一致。但Bag和List不支援sort排序。

order-by:在元素中增加order-by屬性(比如order-by="address desc" )可以實現資料庫排序. 該特性利用了JDK1.4+ 中的LinkedHashSet以及LinkedHashMap, 由此必須在環境JDK1.4以上才可成功。Set, Map, Bag支援, List不支援該特性.

配置樣本:映像檔案中設定sort屬性,例如若為Set,則如下設定:

<set name="addrs" table="ADDRS" sort="natural">

        <key column="USER_ID"/>

        <element type="string" column="ADDRESS" not-null="true"/>

</set>

如果是Map的話,則如下設定:

<map name="files" table="FILES" sort="natural">

    <key column="USER_ID"/>

    <index column="DESCRIPTION" type="string"/>

    <element type="string" column="FILENAME" not-null="true"/>

</map>

在Set中是這麼設定的:

<set name="addrs" table="ADDRS" order-by="ADDRESS desc">

        <key column="USER_ID"/>

        <element type="string" column="ADDRESS" not-null="true"/>

</set>

在Map中也是相同的設定方式,您也可以利用資料庫中的函式功能,例如:

<map name="files" table="FILES" order-by="lower(FILENAME)">

    <key column="USER_ID"/>

    <index column="DESCRIPTION" type="string"/>

    <element type="string" column="FILENAME" not-null="true"/>

</map>

 

在查詢中,還需要考慮的一個問題: 在設定了1 對多這種關係之後, 查詢將會出現n +1 問題。 
1 )1 對多,在1 方尋找得到了n個對象, 那麼又需要將n 個對象關聯的集合取出,於是本來的一條sql查詢變成了n +1 條 
2)多對1 ,在多方查詢得到了m個對象,那麼也會將m個對象對應的1 方的對象取出, 也變成了m+1

解決n+1問題的思路是利用Hibernate提供的兩種檢索策略:延遲檢索策略和迫切左外串連檢索策略。延遲檢索策略能避免載入應用程式不需要訪問的關聯對象,迫切左外串連檢索策略則充分利用了SQL的外串連查詢功能,減少select語句的數目。

?   利用延遲檢索策略,則在配置中將lazy設定為true,lazy=true時不會立刻查詢關聯對象,只有當需要關聯對象(訪問其屬性,非id欄位)時才會發生查詢動作。使用注釋方式,則在本類DTO中有關聯外表的表對象的聲明,在其的get方法上面加上一個@fetch=fetchtype.lazy。(Hibernate3預設是lazy=true)。

雖然利用延遲檢索可以避免執行多餘的select語句載入應用程式不需要訪問的對象,因此能提高檢索效能,並且節省了記憶體空間;但是,應用程式如果希望訪問游離狀態代理類執行個體,必須保證資料對象在持久化狀態時已經被初始化。

 

?   利用左外串連檢索策略,則在配置中設定outer-join=true,(或採用註解方式:@ManyToOne() @Fetch(FetchMode.JOIN)

左外串連檢索策略對應用程式完全透明,不管對象處於持久化狀態,還是游離狀態,應用程式都可以方便地從一個對象導航到與它關聯的對象。同時由於使用了外串連,select語句數目得到了減少;但左串連也可能會載入應用程式不需要訪問的對象浪費許多記憶體空間,並且複雜的資料庫表串連也會影響檢索效能,不利用進行SQL最佳化;

對於迫切左外串連檢索,query的集合檢索並不適用,它會採用立即檢索策略,同時,我們還需要通過 hibernate.max_fetch_depth屬性來控制外串連的深度,由於外串連使select語句的複雜度提高,多表之間的關聯將是很耗時的操作,而且關聯越深查詢的效能會急速下降。

 

其他的思路還有:利用二級緩衝,如果資料在二級緩衝中被命中,則不會再引起SQL查詢。也可以在關聯類別上設定@BatchSize(size=2),此時就只發生兩條語句。

(@org.hibernate.annotations.BatchSize 允許你定義批量擷取該實體的執行個體數量(如:@BatchSize(size=4)). 當載入一特定的實體時,Hibernate將載入在持久上下文中未經初始化的同類型實體,直至批量數量(上限))
在Criteria介面也可以通過設定setFetchMode來設定檢索策略。在網上看到一篇<Hibernate 3如何解決n+1 selects問題>的文章中(如此嵌套沒有嘗試過),提到嵌套多張外部索引鍵關聯表時,如四張表(one,two,three,four)從one一直外部索引鍵關聯到four,用Session中得到One,並從One裡一直取到Four裡的內容的做法。代碼摘下:

session = sessionFactory.openSession();

Criteria criteria = session.createCriteria(One.class);

criteria.add(Expression.eq("COneId",new Integer(1)));

one = (One)criteria.setFetchMode("twos",FetchMode.JOIN).setFetchMode("twos.threes",FetchMode.JOIN).setFetchMode("twos.threes.fours",FetchMode.JOIN).uniqueResult();

session.close();

在用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語句時就寫成聯集查詢的形式。

 

 

 

附:以下參考自http://www.blogjava.net/dreamstone/archive/2007/07/29/133071.html

回顧一下Hibernate實體物件的狀態。在Hibernate中,一個對象定義了三種狀態:transient(瞬態或者自由態)、persistent(持久化狀態)、detached(脫管狀態或者游離態)。

    ?   瞬時狀態(transient):剛剛用new語句建立,還沒有被持久化,不處於session的緩衝中,在資料庫中無相應記錄。處於臨時狀態的java對象稱之為臨時對象。
    ?   持久化對象(persistent):已經被持久化,加入到session的緩衝中,在資料庫中有相應記錄。處於持久化狀態的java對象被稱之為持久化對象,會被session根據持久化對象的屬性變化,自動同步更新資料庫。
    ?   託管(游離)狀態(detached):持久化對象關聯的session關閉後處於託管狀態,沒在Session緩衝中,如果沒有其他程式刪除其對應的紀錄,那麼資料庫中應該有其紀錄。可以繼續修改然後關聯到新的session上,再次成為持久化對象,託管期間的修改會被持久化到DB。這使長時間操作成為可能。

 

這三種狀態可以通過Session的一些API調用實現互相轉化:

    ?   處於游離狀態的執行個體可以通過調用save()、persist()或者saveOrUpdate()方法進行持久化。
    ?   持久化的執行個體可以通過調用 delete()變成脫管狀態。通過get()或load()方法得到的執行個體都是持久化狀態的。
    ?   脫管狀態的執行個體可以通過調用 update()、saveOrUpdate()、lock()或者replicate()進行持久化。

    ?   save()和persist()將會觸發SQL的INSERT操作,delete()會觸發SQL DELETE,而update()或merge()會觸發SQL UPDATE。對持久化(persistent)執行個體的修改在重新整理提交的時候會被檢測到,它也會引起SQL UPDATE。saveOrUpdate()或者replicate()會引發SQL INSERT或者UPDATE

 

這些API的區別如下:

save 和update
        save是把一個新的對象進行儲存;update則是對一個脫管狀態的對象進行儲存。

 

update 和saveOrUpdate
        update()方法操作的對象必須是持久化了的對象,當持久化的對象發生變化時進行儲存,如果該對象在資料庫中不存在,則會拋出異常。saveOrUpdate()方法操作的對象既可以使持久化了的,也可以使沒有持久化的對象。如果是持久化了的對象調用saveOrUpdate()會更新資料庫中的對象;如果是未持久化的對象,則save到資料庫中,相當於新增了一個對象。

 

persist和save
        參考http://opensource.atlassian.com/projects/hibernate/browse/HHH-1682中的一個說明:

I found that a lot of people have the same doubt. To help to solve this issue 
I‘m quoting Christian Bauer:
"In case anybody finds this thread...

persist() is well defined. It makes a transient instance persistent. However, 
it doesn‘t guarantee that the identifier value will be assigned to the persistent 
instance immediately, the assignment might happen at flush time. The spec doesn‘t say
that, which is the problem I have with persist().

persist() also guarantees that it will not execute an INSERT statement if it is 
called outside of transaction boundaries. This is useful in long-running conversations 
with an extended Session/persistence context.A method like persist() is required.

save() does not guarantee the same, it returns an identifier, and if an INSERT 
has to be executed to get the identifier (e.g. "identity" generator, not "sequence"), 
this INSERT happens immediately, no matter if you are inside or outside of a transaction. This is not good in a long-running conversation with an extended Session/persistence context."

簡單翻譯一下上邊的句子的主要內容:
        1,persist把一個瞬態的執行個體持久化,但是並"不保證"標識符被立刻填入到持久化執行個體中,標識符的填入可能被延遲到flush的時間。

        2,persist"保證",當它在一個transaction外部被調用的時候並不觸發一個Sql Insert,這個功能是很有用的,當我們通過繼承Session/persistence context來封裝一個長會話流程的時候,一個persist這樣的函數是需要的。

        3,save"不保證"第2條,它要返回標識符,所以它會立即執行Sql insert,不管是不是在transaction內部還是外部

 

saveOrUpdateCopy、merge和update
       merge是用來代替saveOrUpdateCopy的,參考
http://www.blogjava.net/dreamstone/archive/2007/07/28/133053.html
。Merge:將當前對象的狀態儲存到資料庫中,並不會把該對象轉換成持久化狀態。

      Merge與update的區別在於:當我們使用update的時候,執行完成後,我們提供的對象A的狀態變成持久化狀態,但當我們使用merge的時候,執行完成,我們提供的對象A還是脫管狀態,hibernate或者new了一個B,或者檢索到一個持久對象B,並把我們提供的對象A的所有的值拷貝到這個B,執行完成後B是持久狀態,而我們提供的A還是託管狀態。

 

flush和update
        update操作的是在脫管狀態的對象,而flush是操作的在持久狀態的對象。
        預設情況下,一個持久狀態的對象是不需要update的,只要你更改了對象的值,等待hibernate flush就自動儲存到資料庫了。hibernate flush發生再幾種情況下:
        1,調用某些查詢的時候
        2,transaction commit的時候
        3,手動調用flush的時候

 

lock和update
        update是把一個已經更改過的脫管狀態的對象變成持久狀態;lock是把一個沒有更改過的脫管狀態的對象變成持久狀態。
        對應更改一個記錄的內容,兩個的操作不同:
        update的操作步驟是:
        (1)更改脫管的對象->調用update
        lock的操作步驟是:
         (2)調用lock把對象從脫管狀態變成持久狀態-->更改持久狀態的對象的內容-->等待flush或者手動flush

 

load和get

區別1:Session.load/get方法均可以根據指定的實體類和id從資料庫讀取記錄,並返回與之對應的實體物件。其區別在於:
如果未能發現合格記錄,get方法返回null,而load方法會拋出一個ObjectNotFoundException;load方法可返回實體的代理類執行個體,而get方法永遠直接返回實體類;load方法可以充分利用內部緩衝和二級緩衝中的現有資料,而get方法則僅僅在內部緩衝中進行資料尋找,如沒有發現對應資料,將越過二級緩衝,直接調用SQL完成資料讀取。

區別2:load支援消極式載入,get不支援消極式載入。load 在載入的時候會根據載入策略來載入東西,載入策略預設為消極式載入,即只載入id.,如果需要用其它資料,必須在session關閉之前,去載入某一 個屬性。lazy="true" or "false" 如果載入策略是立即載入,那麼它在載入時會把資料資訊全部載入,這個時候即使,關閉session,因為資料已經全部載入了,也能取得資料。get 會直接採用立即載入策略載入資料,不管你配置的是消極式載入還是立即載入。關於立即載入和消極式載入 不僅只對自己這張表,將來表與表之間有關係時,一樣會起作用。

無論是get還是load,都會首先尋找緩衝(一級緩衝),如果沒有,才會去資料庫尋找,調用clear()方法,可以強制清除session緩衝,調用flush()方法可以強制進行從記憶體到資料庫的同步。

 

Query的list和iterator方法的不同:

list不會使用緩衝,而iterate會先取資料庫select id出來,然後一個id一個id的load,如果在緩衝裡面有,就從緩衝取,沒有的話就去資料庫load。

不管是list方法還是iterate方法,第一次查詢的時候,它們的查詢方式很它們平時的方式是一樣的,list執行一條sql,iterate執行1+N條,多出來的行為是它們填充了緩衝查詢快取需要開啟相關類的class緩衝。list和iterate方法第一次執行的時候,都是既填充查詢快取又填充class緩衝的。

這裡還有一個很容易被忽視的重要問題,即開啟查詢快取以後,即使是list方法也可能遇到1+N的問題!

a>list取出所有

b>iterate取出id,等要用的時候再根據id取出對象

c>session中的list第二次發出,仍然會到資料庫查詢

d>iterate第二次,首先找session級緩衝

getCurrentSession()一定要在事務中使用!!!

Hibernate大量操作(二)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.