<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    【永恒的瞬間】
    ?Give me hapy ?

    第 20 章 提升性能

    20.1.  抓取策略(Fetching strategies)

    抓取策略(fetching strategy) 是指:當(dāng)應(yīng)用程序需要在(Hibernate實(shí)體對(duì)象圖的)關(guān)聯(lián)關(guān)系間進(jìn)行導(dǎo)航的時(shí)候, Hibernate如何獲取關(guān)聯(lián)對(duì)象的策略。抓取策略可以在O/R映射的元數(shù)據(jù)中聲明,也可以在特定的HQL 或條件查詢(Criteria Query)中重載聲明。

    Hibernate3 定義了如下幾種抓取策略:

    • 連接抓取(Join fetching) - Hibernate通過(guò) 在SELECT語(yǔ)句使用OUTER JOIN(外連接)來(lái) 獲得對(duì)象的關(guān)聯(lián)實(shí)例或者關(guān)聯(lián)集合。

    • 查詢抓取(Select fetching) - 另外發(fā)送一條 SELECT 語(yǔ)句抓取當(dāng)前對(duì)象的關(guān)聯(lián)實(shí)體或集合。除非你顯式的指定lazy="false"禁止 延遲抓取(lazy fetching),否則只有當(dāng)你真正訪問(wèn)關(guān)聯(lián)關(guān)系的時(shí)候,才會(huì)執(zhí)行第二條select語(yǔ)句。

    • 子查詢抓取(Subselect fetching) - 另外發(fā)送一條SELECT 語(yǔ)句抓取在前面查詢到(或者抓取到)的所有實(shí)體對(duì)象的關(guān)聯(lián)集合。除非你顯式的指定lazy="false" 禁止延遲抓取(lazy fetching),否則只有當(dāng)你真正訪問(wèn)關(guān)聯(lián)關(guān)系的時(shí)候,才會(huì)執(zhí)行第二條select語(yǔ)句。

    • 批量抓取(Batch fetching) - 對(duì)查詢抓取的優(yōu)化方案, 通過(guò)指定一個(gè)主鍵或外鍵列表,Hibernate使用單條SELECT語(yǔ)句獲取一批對(duì)象實(shí)例或集合。

    Hibernate會(huì)區(qū)分下列各種情況:

    • Immediate fetching,立即抓取 - 當(dāng)宿主被加載時(shí),關(guān)聯(lián)、集合或?qū)傩员涣⒓醋ト ?

    • Lazy collection fetching,延遲集合抓取- 直到應(yīng)用程序?qū)线M(jìn)行了一次操作時(shí),集合才被抓取。(對(duì)集合而言這是默認(rèn)行為。)

    • Proxy fetching,代理抓取 - 對(duì)返回單值的關(guān)聯(lián)而言,當(dāng)其某個(gè)方法被調(diào)用,而非對(duì)其關(guān)鍵字進(jìn)行g(shù)et操作時(shí)才抓取。

    • Lazy attribute fetching,屬性延遲加載 - 對(duì)屬性或返回單值的關(guān)聯(lián)而言,當(dāng)其實(shí)例變量被訪問(wèn)的時(shí)候進(jìn)行抓取(需要運(yùn)行時(shí)字節(jié)碼強(qiáng)化)。這一方法很少是必要的。

    這里有兩個(gè)正交的概念:關(guān)聯(lián)何時(shí)被抓取,以及被如何抓取(會(huì)采用什么樣的SQL語(yǔ)句)。不要混淆它們!我們使用抓取來(lái)改善性能。我們使用延遲來(lái)定義一些契約,對(duì)某特定類的某個(gè)脫管的實(shí)例,知道有哪些數(shù)據(jù)是可以使用的。

    20.1.1. 操作延遲加載的關(guān)聯(lián)

    默認(rèn)情況下,Hibernate 3對(duì)集合使用延遲select抓取,對(duì)返回單值的關(guān)聯(lián)使用延遲代理抓取。對(duì)幾乎是所有的應(yīng)用而言,其絕大多數(shù)的關(guān)聯(lián),這種策略都是有效的。

    注意:假若你設(shè)置了hibernate.default_batch_fetch_size,Hibernate會(huì)對(duì)延遲加載采取批量抓取優(yōu)化措施(這種優(yōu)化也可能會(huì)在更細(xì)化的級(jí)別打開(kāi))。

    然而,你必須了解延遲抓取帶來(lái)的一個(gè)問(wèn)題。在一個(gè)打開(kāi)的Hibernate session上下文之外調(diào)用延遲集合會(huì)導(dǎo)致一次意外。比如:

    s = sessions.openSession();
    Transaction tx = s.beginTransaction();
    User u = (User) s.createQuery("from User u where u.name=:userName")
    .setString("userName", userName).uniqueResult();
    Map permissions = u.getPermissions();
    tx.commit();
    s.close();
    Integer accessLevel = (Integer) permissions.get("accounts");  // Error!

    Session關(guān)閉后,permessions集合將是未實(shí)例化的、不再可用,因此無(wú)法正常載入其狀態(tài)。 Hibernate對(duì)脫管對(duì)象不支持延遲實(shí)例化. 這里的修改方法是:將permissions讀取數(shù)據(jù)的代碼 移到tx.commit()之前。

    除此之外,通過(guò)對(duì)關(guān)聯(lián)映射指定lazy="false",我們也可以使用非延遲的集合或關(guān)聯(lián)。但是, 對(duì)絕大部分集合來(lái)說(shuō),更推薦使用延遲方式抓取數(shù)據(jù)。如果在你的對(duì)象模型中定義了太多的非延遲關(guān)聯(lián),Hibernate最終幾乎需要在每個(gè)事務(wù)中載入整個(gè)數(shù)據(jù)庫(kù)到內(nèi)存中!

    但是,另一方面,在一些特殊的事務(wù)中,我們也經(jīng)常需要使用到連接抓取(它本身上就是非延遲的),以代替查詢抓取。 下面我們將會(huì)很快明白如何具體的定制Hibernate中的抓取策略。在Hibernate3中,具體選擇哪種抓取策略的機(jī)制是和選擇 單值關(guān)聯(lián)或集合關(guān)聯(lián)相一致的。

    20.1.2.  調(diào)整抓取策略(Tuning fetch strategies)

    查詢抓取(默認(rèn)的)在N+1查詢的情況下是極其脆弱的,因此我們可能會(huì)要求在映射文檔中定義使用連接抓取:

    <set name="permissions"
    fetch="join">
    <key column="userId"/>
    <one-to-many class="Permission"/>
    </set
    <many-to-one name="mother" class="Cat" fetch="join"/>

    在映射文檔中定義的抓取策略將會(huì)有產(chǎn)生以下影響:

    • 通過(guò)get()load()方法取得數(shù)據(jù)。

    • 只有在關(guān)聯(lián)之間進(jìn)行導(dǎo)航時(shí),才會(huì)隱式的取得數(shù)據(jù)(延遲抓取)。

    • 條件查詢

    通常情況下,我們并不使用映射文檔進(jìn)行抓取策略的定制。更多的是,保持其默認(rèn)值,然后在特定的事務(wù)中, 使用HQL的左連接抓取(left join fetch) 對(duì)其進(jìn)行重載。這將通知 Hibernate在第一次查詢中使用外部關(guān)聯(lián)(outer join),直接得到其關(guān)聯(lián)數(shù)據(jù)。 在條件查詢 API中,應(yīng)該調(diào)用 setFetchMode(FetchMode.JOIN)語(yǔ)句。

    也許你喜歡僅僅通過(guò)條件查詢,就可以改變get()load()語(yǔ)句中的數(shù)據(jù)抓取策略。例如:

    User user = (User) session.createCriteria(User.class)
    .setFetchMode("permissions", FetchMode.JOIN)
    .add( Restrictions.idEq(userId) )
    .uniqueResult();

    (這就是其他ORM解決方案的“抓取計(jì)劃(fetch plan)”在Hibernate中的等價(jià)物。)

    截然不同的一種避免N+1次查詢的方法是,使用二級(jí)緩存。

    20.1.3. 單端關(guān)聯(lián)代理(Single-ended association proxies)

    在Hinerbate中,對(duì)集合的延遲抓取的采用了自己的實(shí)現(xiàn)方法。但是,對(duì)于單端關(guān)聯(lián)的延遲抓取,則需要采用 其他不同的機(jī)制。單端關(guān)聯(lián)的目標(biāo)實(shí)體必須使用代理,Hihernate在運(yùn)行期二進(jìn)制級(jí)(通過(guò)優(yōu)異的CGLIB庫(kù)), 為持久對(duì)象實(shí)現(xiàn)了延遲載入代理。

    默認(rèn)的,Hibernate3將會(huì)為所有的持久對(duì)象產(chǎn)生代理(在啟動(dòng)階段),然后使用他們實(shí)現(xiàn) 多對(duì)一(many-to-one)關(guān)聯(lián)和一對(duì)一(one-to-one) 關(guān)聯(lián)的延遲抓取。

    在映射文件中,可以通過(guò)設(shè)置proxy屬性為目標(biāo)class聲明一個(gè)接口供代理接口使用。 默認(rèn)的,Hibernate將會(huì)使用該類的一個(gè)子類。 注意:被代理的類必須實(shí)現(xiàn)一個(gè)至少包可見(jiàn)的默認(rèn)構(gòu)造函數(shù),我們建議所有的持久類都應(yīng)擁有這樣的構(gòu)造函數(shù)

    在如此方式定義一個(gè)多態(tài)類的時(shí)候,有許多值得注意的常見(jiàn)性的問(wèn)題,例如:

    <class name="Cat" proxy="Cat">
    ......
    <subclass name="DomesticCat">
    .....
    </subclass>
    </class>

    首先,Cat實(shí)例永遠(yuǎn)不可以被強(qiáng)制轉(zhuǎn)換為DomesticCat, 即使它本身就是DomesticCat實(shí)例。

    Cat cat = (Cat) session.load(Cat.class, id);  // instantiate a proxy (does not hit the db)
    if ( cat.isDomesticCat() ) {                  // hit the db to initialize the proxy
    DomesticCat dc = (DomesticCat) cat;       // Error!
    ....
    }

    其次,代理的“==”可能不再成立。

    Cat cat = (Cat) session.load(Cat.class, id);            // instantiate a Cat proxy
    DomesticCat dc =
    (DomesticCat) session.load(DomesticCat.class, id);  // acquire new DomesticCat proxy!
    System.out.println(cat==dc);                            // false

    雖然如此,但實(shí)際情況并沒(méi)有看上去那么糟糕。雖然我們現(xiàn)在有兩個(gè)不同的引用,分別指向這兩個(gè)不同的代理對(duì)象, 但實(shí)際上,其底層應(yīng)該是同一個(gè)實(shí)例對(duì)象:

    cat.setWeight(11.0);  // hit the db to initialize the proxy
    System.out.println( dc.getWeight() );  // 11.0

    第三,你不能對(duì)“final類”或“具有final方法的類”使用CGLIB代理。

    最后,如果你的持久化對(duì)象在實(shí)例化時(shí)需要某些資源(例如,在實(shí)例化方法、默認(rèn)構(gòu)造方法中), 那么代理對(duì)象也同樣需要使用這些資源。實(shí)際上,代理類是持久化類的子類。

    這些問(wèn)題都源于Java的單根繼承模型的天生限制。如果你希望避免這些問(wèn)題,那么你的每個(gè)持久化類必須實(shí)現(xiàn)一個(gè)接口, 在此接口中已經(jīng)聲明了其業(yè)務(wù)方法。然后,你需要在映射文檔中再指定這些接口。例如:

    <class name="CatImpl" proxy="Cat">
    ......
    <subclass name="DomesticCatImpl" proxy="DomesticCat">
    .....
    </subclass>
    </class>

    這里CatImpl實(shí)現(xiàn)了Cat接口, DomesticCatImpl實(shí)現(xiàn)DomesticCat接口。 在load()iterate()方法中就會(huì)返回 CatDomesticCat的代理對(duì)象。 (注意list()并不會(huì)返回代理對(duì)象。)

    Cat cat = (Cat) session.load(CatImpl.class, catid);
    Iterator iter = session.iterate("from CatImpl as cat where cat.name='fritz'");
    Cat fritz = (Cat) iter.next();

    這里,對(duì)象之間的關(guān)系也將被延遲載入。這就意味著,你應(yīng)該將屬性聲明為Cat,而不是CatImpl

    但是,在有些方法中是不需要使用代理的。例如:

    • equals()方法,如果持久類沒(méi)有重載equals()方法。

    • hashCode()方法,如果持久類沒(méi)有重載hashCode()方法。

    • 標(biāo)志符的getter方法。

    Hibernate將會(huì)識(shí)別出那些重載了equals()、或hashCode()方法的持久化類。

    20.1.4. 實(shí)例化集合和代理(Initializing collections and proxies)

    Session范圍之外訪問(wèn)未初始化的集合或代理,Hibernate將會(huì)拋出LazyInitializationException異常。 也就是說(shuō),在分離狀態(tài)下,訪問(wèn)一個(gè)實(shí)體所擁有的集合,或者訪問(wèn)其指向代理的屬性時(shí),會(huì)引發(fā)此異常。

    有時(shí)候我們需要保證某個(gè)代理或者集合在Session關(guān)閉前就已經(jīng)被初始化了。 當(dāng)然,我們可以通過(guò)強(qiáng)行調(diào)用cat.getSex()或者cat.getKittens().size()之類的方法來(lái)確保這一點(diǎn)。 但是這樣的程序會(huì)造成讀者的疑惑,也不符合通常的代碼規(guī)范。

    靜態(tài)方法Hibernate.initialized() 為你的應(yīng)用程序提供了一個(gè)便捷的途徑來(lái)延遲加載集合或代理。 只要它的Session處于open狀態(tài),Hibernate.initialize(cat) 將會(huì)為cat強(qiáng)制對(duì)代理實(shí)例化。 同樣,Hibernate.initialize( cat.getKittens() ) 對(duì)kittens的集合具有同樣的功能。

    還有另外一種選擇,就是保持Session一直處于open狀態(tài),直到所有需要的集合或代理都被載入。 在某些應(yīng)用架構(gòu)中,特別是對(duì)于那些使用Hibernate進(jìn)行數(shù)據(jù)訪問(wèn)的代碼,以及那些在不同應(yīng)用層和不同物理進(jìn)程中使用Hibernate的代碼。 在集合實(shí)例化時(shí),如何保證Session處于open狀態(tài)經(jīng)常會(huì)是一個(gè)問(wèn)題。有兩種方法可以解決此問(wèn)題:

    • 在一個(gè)基于Web的應(yīng)用中,可以利用servlet過(guò)濾器(filter),在用戶請(qǐng)求(request)結(jié)束、頁(yè)面生成 結(jié)束時(shí)關(guān)閉Session(這里使用了在展示層保持打開(kāi)Session模式(Open Session in View)), 當(dāng)然,這將依賴于應(yīng)用框架中異常需要被正確的處理。在返回界面給用戶之前,乃至在生成界面過(guò)程中發(fā)生異常的情況下, 正確關(guān)閉Session和結(jié)束事務(wù)將是非常重要的, Servlet過(guò)濾器必須如此訪問(wèn)Session,才能保證正確使用Session。 我們推薦使用ThreadLocal 變量保存當(dāng)前的Session (可以參考第 1.4 節(jié) “與Cat同樂(lè)”的例子實(shí)現(xiàn))。

    • 在一個(gè)擁有單獨(dú)業(yè)務(wù)層的應(yīng)用中,業(yè)務(wù)層必須在返回之前,為web層“準(zhǔn)備”好其所需的數(shù)據(jù)集合。這就意味著 業(yè)務(wù)層應(yīng)該載入所有表現(xiàn)層/web層所需的數(shù)據(jù),并將這些已實(shí)例化完畢的數(shù)據(jù)返回。通常,應(yīng)用程序應(yīng)該 為web層所需的每個(gè)集合調(diào)用Hibernate.initialize()(這個(gè)調(diào)用必須發(fā)生咱session關(guān)閉之前); 或者使用帶有FETCH從句,或FetchMode.JOIN的Hibernate查詢, 事先取得所有的數(shù)據(jù)集合。如果你在應(yīng)用中使用了Command模式,代替Session Facade , 那么這項(xiàng)任務(wù)將會(huì)變得簡(jiǎn)單的多。

    • 你也可以通過(guò)merge()lock()方法,在訪問(wèn)未實(shí)例化的集合(或代理)之前, 為先前載入的對(duì)象綁定一個(gè)新的Session。 顯然,Hibernate將不會(huì),也不應(yīng)該自動(dòng)完成這些任務(wù),因?yàn)檫@將引入一個(gè)特殊的事務(wù)語(yǔ)義。

    有時(shí)候,你并不需要完全實(shí)例化整個(gè)大的集合,僅需要了解它的部分信息(例如其大小)、或者集合的部分內(nèi)容。

    你可以使用集合過(guò)濾器得到其集合的大小,而不必實(shí)例化整個(gè)集合:

    ( (Integer) s.createFilter( collection, "select count(*)" ).list().get(0) ).intValue()

    這里的createFilter()方法也可以被用來(lái)有效的抓取集合的部分內(nèi)容,而無(wú)需實(shí)例化整個(gè)集合:

    s.createFilter( lazyCollection, "").setFirstResult(0).setMaxResults(10).list();

    20.1.5. 使用批量抓取(Using batch fetching)

    Hibernate可以充分有效的使用批量抓取,也就是說(shuō),如果僅一個(gè)訪問(wèn)代理(或集合),那么Hibernate將不載入其他未實(shí)例化的代理。 批量抓取是延遲查詢抓取的優(yōu)化方案,你可以在兩種批量抓取方案之間進(jìn)行選擇:在類級(jí)別和集合級(jí)別。

    類/實(shí)體級(jí)別的批量抓取很容易理解。假設(shè)你在運(yùn)行時(shí)將需要面對(duì)下面的問(wèn)題:你在一個(gè)Session中載入了25個(gè) Cat實(shí)例,每個(gè)Cat實(shí)例都擁有一個(gè)引用成員owner, 其指向Person,而Person類是代理,同時(shí)lazy="true"。 如果你必須遍歷整個(gè)cats集合,對(duì)每個(gè)元素調(diào)用getOwner()方法,Hibernate將會(huì)默認(rèn)的執(zhí)行25次SELECT查詢, 得到其owner的代理對(duì)象。這時(shí),你可以通過(guò)在映射文件的Person屬性,顯式聲明batch-size,改變其行為:

    <class name="Person" batch-size="10">...</class>

    隨之,Hibernate將只需要執(zhí)行三次查詢,分別為10、10、 5。

    你也可以在集合級(jí)別定義批量抓取。例如,如果每個(gè)Person都擁有一個(gè)延遲載入的Cats集合, 現(xiàn)在,Sesssion中載入了10個(gè)person對(duì)象,遍歷person集合將會(huì)引起10次SELECT查詢, 每次查詢都會(huì)調(diào)用getCats()方法。如果你在Person的映射定義部分,允許對(duì)cats批量抓取, 那么,Hibernate將可以預(yù)先抓取整個(gè)集合。請(qǐng)看例子:

    <class name="Person">
    <set name="cats" batch-size="3">
    ...
    </set>
    </class>

    如果整個(gè)的batch-size是3(筆誤?),那么Hibernate將會(huì)分四次執(zhí)行SELECT查詢, 按照3、3、3、1的大小分別載入數(shù)據(jù)。這里的每次載入的數(shù)據(jù)量還具體依賴于當(dāng)前Session中未實(shí)例化集合的個(gè)數(shù)。

    如果你的模型中有嵌套的樹(shù)狀結(jié)構(gòu),例如典型的帳單-原料結(jié)構(gòu)(bill-of-materials pattern),集合的批量抓取是非常有用的。 (盡管在更多情況下對(duì)樹(shù)進(jìn)行讀取時(shí),嵌套集合(nested set)原料路徑(materialized path)(××) 是更好的解決方法。)

    20.1.6. 使用子查詢抓取(Using subselect fetching)

    假若一個(gè)延遲集合或單值代理需要抓取,Hibernate會(huì)使用一個(gè)subselect重新運(yùn)行原來(lái)的查詢,一次性讀入所有的實(shí)例。這和批量抓取的實(shí)現(xiàn)方法是一樣的,不會(huì)有破碎的加載。

    20.1.7. 使用延遲屬性抓取(Using lazy property fetching)

    Hibernate3對(duì)單獨(dú)的屬性支持延遲抓取,這項(xiàng)優(yōu)化技術(shù)也被稱為組抓取(fetch groups)。 請(qǐng)注意,該技術(shù)更多的屬于市場(chǎng)特性。在實(shí)際應(yīng)用中,優(yōu)化行讀取比優(yōu)化列讀取更重要。但是,僅載入類的部分屬性在某些特定情況下會(huì)有用,例如在原有表中擁有幾百列數(shù)據(jù)、數(shù)據(jù)模型無(wú)法改動(dòng)的情況下。

    可以在映射文件中對(duì)特定的屬性設(shè)置lazy,定義該屬性為延遲載入。

    <class name="Document">
    <id name="id">
    <generator class="native"/>
    </id>
    <property name="name" not-null="true" length="50"/>
    <property name="summary" not-null="true" length="200" lazy="true"/>
    <property name="text" not-null="true" length="2000" lazy="true"/>
    </class>

    屬性的延遲載入要求在其代碼構(gòu)建時(shí)加入二進(jìn)制指示指令(bytecode instrumentation),如果你的持久類代碼中未含有這些指令, Hibernate將會(huì)忽略這些屬性的延遲設(shè)置,仍然將其直接載入。

    你可以在Ant的Task中,進(jìn)行如下定義,對(duì)持久類代碼加入“二進(jìn)制指令。”

    <target name="instrument" depends="compile">
    <taskdef name="instrument" classname="org.hibernate.tool.instrument.InstrumentTask">
    <classpath path="${jar.path}"/>
    <classpath path="${classes.dir}"/>
    <classpath refid="lib.class.path"/>
    </taskdef>
    <instrument verbose="true">
    <fileset dir="${testclasses.dir}/org/hibernate/auction/model">
    <include name="*.class"/>
    </fileset>
    </instrument>
    </target>

    還有一種可以優(yōu)化的方法,它使用HQL或條件查詢的投影(projection)特性,可以避免讀取非必要的列, 這一點(diǎn)至少對(duì)只讀事務(wù)是非常有用的。它無(wú)需在代碼構(gòu)建時(shí)“二進(jìn)制指令”處理,因此是一個(gè)更加值得選擇的解決方法。

    有時(shí)你需要在HQL中通過(guò)抓取所有屬性,強(qiáng)行抓取所有內(nèi)容。

    20.2. 二級(jí)緩存(The Second Level Cache)

    Hibernate的Session在事務(wù)級(jí)別進(jìn)行持久化數(shù)據(jù)的緩存操作。 當(dāng)然,也有可能分別為每個(gè)類(或集合),配置集群、或JVM級(jí)別(SessionFactory級(jí)別)的緩存。 你甚至可以為之插入一個(gè)集群的緩存。注意,緩存永遠(yuǎn)不知道其他應(yīng)用程序?qū)Τ志没瘋}(cāng)庫(kù)(數(shù)據(jù)庫(kù))可能進(jìn)行的修改 (即使可以將緩存數(shù)據(jù)設(shè)定為定期失效)。

    默認(rèn)情況下,Hibernate使用EHCache進(jìn)行JVM級(jí)別的緩存(目前,Hibernate已經(jīng)廢棄了對(duì)JCS的支持,未來(lái)版本中將會(huì)去掉它)。 你可以通過(guò)設(shè)置hibernate.cache.provider_class屬性,指定其他的緩存策略, 該緩存策略必須實(shí)現(xiàn)org.hibernate.cache.CacheProvider接口。

    表 20.1.  緩存策略提供商(Cache Providers)

    Cache Provider class Type Cluster Safe Query Cache Supported
    Hashtable (not intended for production use) org.hibernate.cache.HashtableCacheProvider memory   yes
    EHCache org.hibernate.cache.EhCacheProvider memory, disk   yes
    OSCache org.hibernate.cache.OSCacheProvider memory, disk   yes
    SwarmCache org.hibernate.cache.SwarmCacheProvider clustered (ip multicast) yes (clustered invalidation)  
    JBoss TreeCache org.hibernate.cache.TreeCacheProvider clustered (ip multicast), transactional yes (replication) yes (clock sync req.)

    20.2.1. 緩存映射(Cache mappings)

    類或者集合映射的“<cache>元素”可以有下列形式:

    <cache
    usage="transactional|read-write|nonstrict-read-write|read-only"  (1)
    />
    (1)

    usage說(shuō)明了緩存的策略: transactionalread-writenonstrict-read-writeread-only

    另外(首選?), 你可以在hibernate.cfg.xml中指定<class-cache><collection-cache> 元素。

    這里的usage 屬性指明了緩存并發(fā)策略(cache concurrency strategy)

    20.2.2. 策略:只讀緩存(Strategy: read only)

    如果你的應(yīng)用程序只需讀取一個(gè)持久化類的實(shí)例,而無(wú)需對(duì)其修改, 那么就可以對(duì)其進(jìn)行只讀 緩存。這是最簡(jiǎn)單,也是實(shí)用性最好的方法。甚至在集群中,它也能完美地運(yùn)作。

    <class name="eg.Immutable" mutable="false">
    <cache usage="read-only"/>
    ....
    </class>

    20.2.3.  策略:讀/寫緩存(Strategy: read/write)

    如果應(yīng)用程序需要更新數(shù)據(jù),那么使用讀/寫緩存 比較合適。 如果應(yīng)用程序要求“序列化事務(wù)”的隔離級(jí)別(serializable transaction isolation level),那么就決不能使用這種緩存策略。 如果在JTA環(huán)境中使用緩存,你必須指定hibernate.transaction.manager_lookup_class屬性的值, 通過(guò)它,Hibernate才能知道該應(yīng)用程序中JTA的TransactionManager的具體策略。 在其它環(huán)境中,你必須保證在Session.close()、或Session.disconnect()調(diào)用前, 整個(gè)事務(wù)已經(jīng)結(jié)束。 如果你想在集群環(huán)境中使用此策略,你必須保證底層的緩存實(shí)現(xiàn)支持鎖定(locking)。Hibernate內(nèi)置的緩存策略并不支持鎖定功能。

    <class name="eg.Cat" .... >
    <cache usage="read-write"/>
    ....
    <set name="kittens" ... >
    <cache usage="read-write"/>
    ....
    </set>
    </class>

    20.2.4.  策略:非嚴(yán)格讀/寫緩存(Strategy: nonstrict read/write)

    如果應(yīng)用程序只偶爾需要更新數(shù)據(jù)(也就是說(shuō),兩個(gè)事務(wù)同時(shí)更新同一記錄的情況很不常見(jiàn)),也不需要十分嚴(yán)格的事務(wù)隔離, 那么比較適合使用非嚴(yán)格讀/寫緩存策略。如果在JTA環(huán)境中使用該策略, 你必須為其指定hibernate.transaction.manager_lookup_class屬性的值, 在其它環(huán)境中,你必須保證在Session.close()、或Session.disconnect()調(diào)用前, 整個(gè)事務(wù)已經(jīng)結(jié)束。

    20.2.5.  策略:事務(wù)緩存(transactional)

    Hibernate的事務(wù)緩存策略提供了全事務(wù)的緩存支持, 例如對(duì)JBoss TreeCache的支持。這樣的緩存只能用于JTA環(huán)境中,你必須指定 為其hibernate.transaction.manager_lookup_class屬性。

    沒(méi)有一種緩存提供商能夠支持上列的所有緩存并發(fā)策略。下表中列出了各種提供器、及其各自適用的并發(fā)策略。

    表 20.2.  各種緩存提供商對(duì)緩存并發(fā)策略的支持情況(Cache Concurrency Strategy Support)

    Cache read-only nonstrict-read-write read-write transactional
    Hashtable (not intended for production use) yes yes yes  
    EHCache yes yes yes  
    OSCache yes yes yes  
    SwarmCache yes yes    
    JBoss TreeCache yes     yes

    20.3.  管理緩存(Managing the caches)

    無(wú)論何時(shí),當(dāng)你給save()update()saveOrUpdate()方法傳遞一個(gè)對(duì)象時(shí),或使用load()get()list()iterate()scroll()方法獲得一個(gè)對(duì)象時(shí), 該對(duì)象都將被加入到Session的內(nèi)部緩存中。

    當(dāng)隨后flush()方法被調(diào)用時(shí),對(duì)象的狀態(tài)會(huì)和數(shù)據(jù)庫(kù)取得同步。 如果你不希望此同步操作發(fā)生,或者你正處理大量對(duì)象、需要對(duì)有效管理內(nèi)存時(shí),你可以調(diào)用evict() 方法,從一級(jí)緩存中去掉這些對(duì)象及其集合。

    ScrollableResult cats = sess.createQuery("from Cat as cat").scroll(); //a huge result set
    while ( cats.next() ) {
    Cat cat = (Cat) cats.get(0);
    doSomethingWithACat(cat);
    sess.evict(cat);
    }

    Session還提供了一個(gè)contains()方法,用來(lái)判斷某個(gè)實(shí)例是否處于當(dāng)前session的緩存中。

    如若要把所有的對(duì)象從session緩存中徹底清除,則需要調(diào)用Session.clear()

    對(duì)于二級(jí)緩存來(lái)說(shuō),在SessionFactory中定義了許多方法, 清除緩存中實(shí)例、整個(gè)類、集合實(shí)例或者整個(gè)集合。

    sessionFactory.evict(Cat.class, catId); //evict a particular Cat
    sessionFactory.evict(Cat.class);  //evict all Cats
    sessionFactory.evictCollection("Cat.kittens", catId); //evict a particular collection of kittens
    sessionFactory.evictCollection("Cat.kittens"); //evict all kitten collections

    CacheMode參數(shù)用于控制具體的Session如何與二級(jí)緩存進(jìn)行交互。

    • CacheMode.NORMAL - 從二級(jí)緩存中讀、寫數(shù)據(jù)。

    • CacheMode.GET - 從二級(jí)緩存中讀取數(shù)據(jù),僅在數(shù)據(jù)更新時(shí)對(duì)二級(jí)緩存寫數(shù)據(jù)。

    • CacheMode.PUT - 僅向二級(jí)緩存寫數(shù)據(jù),但不從二級(jí)緩存中讀數(shù)據(jù)。

    • CacheMode.REFRESH - 僅向二級(jí)緩存寫數(shù)據(jù),但不從二級(jí)緩存中讀數(shù)據(jù)。通過(guò) hibernate.cache.use_minimal_puts的設(shè)置,強(qiáng)制二級(jí)緩存從數(shù)據(jù)庫(kù)中讀取數(shù)據(jù),刷新緩存內(nèi)容。

    如若需要查看二級(jí)緩存或查詢緩存區(qū)域的內(nèi)容,你可以使用統(tǒng)計(jì)(Statistics) API。

    Map cacheEntries = sessionFactory.getStatistics()
    .getSecondLevelCacheStatistics(regionName)
    .getEntries();

    此時(shí),你必須手工打開(kāi)統(tǒng)計(jì)選項(xiàng)。可選的,你可以讓Hibernate更人工可讀的方式維護(hù)緩存內(nèi)容。

    hibernate.generate_statistics true
    hibernate.cache.use_structured_entries true

    20.4. 查詢緩存(The Query Cache)

    查詢的結(jié)果集也可以被緩存。只有當(dāng)經(jīng)常使用同樣的參數(shù)進(jìn)行查詢時(shí),這才會(huì)有些用處。 要使用查詢緩存,首先你必須打開(kāi)它:

    hibernate.cache.use_query_cache true

    該設(shè)置將會(huì)創(chuàng)建兩個(gè)緩存區(qū)域 - 一個(gè)用于保存查詢結(jié)果集(org.hibernate.cache.StandardQueryCache); 另一個(gè)則用于保存最近查詢的一系列表的時(shí)間戳(org.hibernate.cache.UpdateTimestampsCache)。 請(qǐng)注意:在查詢緩存中,它并不緩存結(jié)果集中所包含的實(shí)體的確切狀態(tài);它只緩存這些實(shí)體的標(biāo)識(shí)符屬性的值、以及各值類型的結(jié)果。 所以查詢緩存通常會(huì)和二級(jí)緩存一起使用。

    絕大多數(shù)的查詢并不能從查詢緩存中受益,所以Hibernate默認(rèn)是不進(jìn)行查詢緩存的。如若需要進(jìn)行緩存,請(qǐng)調(diào)用 Query.setCacheable(true)方法。這個(gè)調(diào)用會(huì)讓查詢?cè)趫?zhí)行過(guò)程中時(shí)先從緩存中查找結(jié)果, 并將自己的結(jié)果集放到緩存中去。

    如果你要對(duì)查詢緩存的失效政策進(jìn)行精確的控制,你必須調(diào)用Query.setCacheRegion()方法, 為每個(gè)查詢指定其命名的緩存區(qū)域。

    List blogs = sess.createQuery("from Blog blog where blog.blogger = :blogger")
    .setEntity("blogger", blogger)
    .setMaxResults(15)
    .setCacheable(true)
    .setCacheRegion("frontpages")
    .list();

    如果查詢需要強(qiáng)行刷新其查詢緩存區(qū)域,那么你應(yīng)該調(diào)用Query.setCacheMode(CacheMode.REFRESH)方法。 這對(duì)在其他進(jìn)程中修改底層數(shù)據(jù)(例如,不通過(guò)Hibernate修改數(shù)據(jù)),或?qū)δ切┬枰x擇性更新特定查詢結(jié)果集的情況特別有用。 這是對(duì)SessionFactory.evictQueries()的更為有效的替代方案,同樣可以清除查詢緩存區(qū)域。

    20.5.  理解集合性能(Understanding Collection performance)

    前面我們已經(jīng)對(duì)集合進(jìn)行了足夠的討論。本段中,我們將著重講述集合在運(yùn)行時(shí)的事宜。

    20.5.1.  分類(Taxonomy)

    Hibernate定義了三種基本類型的集合:

    • 值數(shù)據(jù)集合

    • 一對(duì)多關(guān)聯(lián)

    • 多對(duì)多關(guān)聯(lián)

    這個(gè)分類是區(qū)分了不同的表和外鍵關(guān)系類型,但是它沒(méi)有告訴我們關(guān)系模型的所有內(nèi)容。 要完全理解他們的關(guān)系結(jié)構(gòu)和性能特點(diǎn),我們必須同時(shí)考慮“用于Hibernate更新或刪除集合行數(shù)據(jù)的主鍵的結(jié)構(gòu)”。 因此得到了如下的分類:

    • 有序集合類

    • 集合(sets)

    • 包(bags)

    所有的有序集合類(maps, lists, arrays)都擁有一個(gè)由<key><index>組成的主鍵。 這種情況下集合類的更新是非常高效的——主鍵已經(jīng)被有效的索引,因此當(dāng)Hibernate試圖更新或刪除一行時(shí),可以迅速找到該行數(shù)據(jù)。

    集合(sets)的主鍵由<key>和其他元素字段構(gòu)成。 對(duì)于有些元素類型來(lái)說(shuō),這很低效,特別是組合元素或者大文本、大二進(jìn)制字段; 數(shù)據(jù)庫(kù)可能無(wú)法有效的對(duì)復(fù)雜的主鍵進(jìn)行索引。 另一方面,對(duì)于一對(duì)多、多對(duì)多關(guān)聯(lián),特別是合成的標(biāo)識(shí)符來(lái)說(shuō),集合也可以達(dá)到同樣的高效性能。( 附注:如果你希望SchemaExport為你的<set>創(chuàng)建主鍵, 你必須把所有的字段都聲明為not-null="true"。)

    <idbag>映射定義了代理鍵,因此它總是可以很高效的被更新。事實(shí)上, <idbag>擁有著最好的性能表現(xiàn)。

    Bag是最差的。因?yàn)閎ag允許重復(fù)的元素值,也沒(méi)有索引字段,因此不可能定義主鍵。 Hibernate無(wú)法判斷出重復(fù)的行。當(dāng)這種集合被更改時(shí),Hibernate將會(huì)先完整地移除 (通過(guò)一個(gè)(in a single DELETE))整個(gè)集合,然后再重新創(chuàng)建整個(gè)集合。 因此Bag是非常低效的。

    請(qǐng)注意:對(duì)于一對(duì)多關(guān)聯(lián)來(lái)說(shuō),“主鍵”很可能并不是數(shù)據(jù)庫(kù)表的物理主鍵。 但就算在此情況下,上面的分類仍然是有用的。(它仍然反映了Hibernate在集合的各數(shù)據(jù)行中是如何進(jìn)行“定位”的。)

    20.5.2.  Lists, maps 和sets用于更新效率最高

    根據(jù)我們上面的討論,顯然有序集合類型和大多數(shù)set都可以在增加、刪除、修改元素中擁有最好的性能。

    可論證的是對(duì)于多對(duì)多關(guān)聯(lián)、值數(shù)據(jù)集合而言,有序集合類比集合(set)有一個(gè)好處。因?yàn)?tt class=literal>Set的內(nèi)在結(jié)構(gòu), 如果“改變”了一個(gè)元素,Hibernate并不會(huì)更新(UPDATE)這一行。 對(duì)于Set來(lái)說(shuō),只有在插入(INSERT)刪除(DELETE) 操作時(shí)“改變”才有效。再次強(qiáng)調(diào):這段討論對(duì)“一對(duì)多關(guān)聯(lián)”并不適用。

    注意到數(shù)組無(wú)法延遲載入,我們可以得出結(jié)論,list, map和idbags是最高效的(非反向)集合類型,set則緊隨其后。 在Hibernate中,set應(yīng)該時(shí)最通用的集合類型,這時(shí)因?yàn)?#8220;set”的語(yǔ)義在關(guān)系模型中是最自然的。

    但是,在設(shè)計(jì)良好的Hibernate領(lǐng)域模型中,我們通常可以看到更多的集合事實(shí)上是帶有inverse="true" 的一對(duì)多的關(guān)聯(lián)。對(duì)于這些關(guān)聯(lián),更新操作將會(huì)在多對(duì)一的這一端進(jìn)行處理。因此對(duì)于此類情況,無(wú)需考慮其集合的更新性能。

    20.5.3.  Bag和list是反向集合類中效率最高的

    在把bag扔進(jìn)水溝之前,你必須了解,在一種情況下,bag的性能(包括list)要比set高得多: 對(duì)于指明了inverse="true"的集合類(比如說(shuō),標(biāo)準(zhǔn)的雙向的一對(duì)多關(guān)聯(lián)), 我們可以在未初始化(fetch)包元素的情況下直接向bag或list添加新元素! 這是因?yàn)?tt class=literal>Collection.add())或者Collection.addAll() 方法 對(duì)bag或者List總是返回true(這點(diǎn)與與Set不同)。因此對(duì)于下面的相同代碼來(lái)說(shuō),速度會(huì)快得多。

    Parent p = (Parent) sess.load(Parent.class, id);
    Child c = new Child();
    c.setParent(p);
    p.getChildren().add(c);  //no need to fetch the collection!
    sess.flush();

    20.5.4.  一次性刪除(One shot delete)

    偶爾的,逐個(gè)刪除集合類中的元素是相當(dāng)?shù)托У摹ibernate并沒(méi)那么笨, 如果你想要把整個(gè)集合都刪除(比如說(shuō)調(diào)用list.clear()),Hibernate只需要一個(gè)DELETE就搞定了。

    假設(shè)我們?cè)谝粋€(gè)長(zhǎng)度為20的集合類中新增加了一個(gè)元素,然后再刪除兩個(gè)。 Hibernate會(huì)安排一條INSERT語(yǔ)句和兩條DELETE語(yǔ)句(除非集合類是一個(gè)bag)。 這當(dāng)然是顯而易見(jiàn)的。

    但是,假設(shè)我們刪除了18個(gè)數(shù)據(jù),只剩下2個(gè),然后新增3個(gè)。則有兩種處理方式:

    • 逐一的刪除這18個(gè)數(shù)據(jù),再新增三個(gè);

    • 刪除整個(gè)集合類(只用一句DELETE語(yǔ)句),然后增加5個(gè)數(shù)據(jù)。

    Hibernate還沒(méi)那么聰明,知道第二種選擇可能會(huì)比較快。 (也許讓Hibernate不這么聰明也是好事,否則可能會(huì)引發(fā)意外的“數(shù)據(jù)庫(kù)觸發(fā)器”之類的問(wèn)題。)

    幸運(yùn)的是,你可以強(qiáng)制使用第二種策略。你需要取消原來(lái)的整個(gè)集合類(解除其引用), 然后再返回一個(gè)新的實(shí)例化的集合類,只包含需要的元素。有些時(shí)候這是非常有用的。

    顯然,一次性刪除并不適用于被映射為inverse="true"的集合。

    20.6.  監(jiān)測(cè)性能(Monitoring performance)

    沒(méi)有監(jiān)測(cè)和性能參數(shù)而進(jìn)行優(yōu)化是毫無(wú)意義的。Hibernate為其內(nèi)部操作提供了一系列的示意圖,因此可以從 每個(gè)SessionFactory抓取其統(tǒng)計(jì)數(shù)據(jù)。

    20.6.1.  監(jiān)測(cè)SessionFactory

    你可以有兩種方式訪問(wèn)SessionFactory的數(shù)據(jù)記錄,第一種就是自己直接調(diào)用 sessionFactory.getStatistics()方法讀取、顯示統(tǒng)計(jì)數(shù)據(jù)。

    此外,如果你打開(kāi)StatisticsService MBean選項(xiàng),那么Hibernate則可以使用JMX技術(shù) 發(fā)布其數(shù)據(jù)記錄。你可以讓應(yīng)用中所有的SessionFactory同時(shí)共享一個(gè)MBean,也可以每個(gè) SessionFactory分配一個(gè)MBean。下面的代碼即是其演示代碼:

    // MBean service registration for a specific SessionFactory
    Hashtable tb = new Hashtable();
    tb.put("type", "statistics");
    tb.put("sessionFactory", "myFinancialApp");
    ObjectName on = new ObjectName("hibernate", tb); // MBean object name
    StatisticsService stats = new StatisticsService(); // MBean implementation
    stats.setSessionFactory(sessionFactory); // Bind the stats to a SessionFactory
    server.registerMBean(stats, on); // Register the Mbean on the server
    // MBean service registration for all SessionFactory's
    Hashtable tb = new Hashtable();
    tb.put("type", "statistics");
    tb.put("sessionFactory", "all");
    ObjectName on = new ObjectName("hibernate", tb); // MBean object name
    StatisticsService stats = new StatisticsService(); // MBean implementation
    server.registerMBean(stats, on); // Register the MBean on the server

    TODO:仍需要說(shuō)明的是:在第一個(gè)例子中,我們直接得到和使用MBean;而在第二個(gè)例子中,在使用MBean之前 我們則需要給出SessionFactory的JNDI名,使用hibernateStatsBean.setSessionFactoryJNDIName("my/JNDI/Name") 得到SessionFactory,然后將MBean保存于其中。

    你可以通過(guò)以下方法打開(kāi)或關(guān)閉SessionFactory的監(jiān)測(cè)功能:

    • 在配置期間,將hibernate.generate_statistics設(shè)置為truefalse

    • 在運(yùn)行期間,則可以可以通過(guò)sf.getStatistics().setStatisticsEnabled(true)hibernateStatsBean.setStatisticsEnabled(true)

    你也可以在程序中調(diào)用clear()方法重置統(tǒng)計(jì)數(shù)據(jù),調(diào)用logSummary() 在日志中記錄(info級(jí)別)其總結(jié)。

    20.6.2.  數(shù)據(jù)記錄(Metrics)

    Hibernate提供了一系列數(shù)據(jù)記錄,其記錄的內(nèi)容包括從最基本的信息到與具體場(chǎng)景的特殊信息。所有的測(cè)量值都可以由 Statistics接口進(jìn)行訪問(wèn),主要分為三類:

    • 使用Session的普通數(shù)據(jù)記錄,例如打開(kāi)的Session的個(gè)數(shù)、取得的JDBC的連接數(shù)等;

    • 實(shí)體、集合、查詢、緩存等內(nèi)容的統(tǒng)一數(shù)據(jù)記錄

    • 和具體實(shí)體、集合、查詢、緩存相關(guān)的詳細(xì)數(shù)據(jù)記錄

    例如:你可以檢查緩存的命中成功次數(shù),緩存的命中失敗次數(shù),實(shí)體、集合和查詢的使用概率,查詢的平均時(shí)間等。請(qǐng)注意 Java中時(shí)間的近似精度是毫秒。Hibernate的數(shù)據(jù)精度和具體的JVM有關(guān),在有些平臺(tái)上其精度甚至只能精確到10秒。

    你可以直接使用getter方法得到全局?jǐn)?shù)據(jù)記錄(例如,和具體的實(shí)體、集合、緩存區(qū)無(wú)關(guān)的數(shù)據(jù)),你也可以在具體查詢中通過(guò)標(biāo)記實(shí)體名、 或HQL、SQL語(yǔ)句得到某實(shí)體的數(shù)據(jù)記錄。請(qǐng)參考StatisticsEntityStatisticsCollectionStatisticsSecondLevelCacheStatistics、 和QueryStatistics的API文檔以抓取更多信息。下面的代碼則是個(gè)簡(jiǎn)單的例子:

    Statistics stats = HibernateUtil.sessionFactory.getStatistics();
    double queryCacheHitCount  = stats.getQueryCacheHitCount();
    double queryCacheMissCount = stats.getQueryCacheMissCount();
    double queryCacheHitRatio =
    queryCacheHitCount / (queryCacheHitCount + queryCacheMissCount);
    log.info("Query Hit ratio:" + queryCacheHitRatio);
    EntityStatistics entityStats =
    stats.getEntityStatistics( Cat.class.getName() );
    long changes =
    entityStats.getInsertCount()
    + entityStats.getUpdateCount()
    + entityStats.getDeleteCount();
    log.info(Cat.class.getName() + " changed " + changes + "times"  );

    如果你想得到所有實(shí)體、集合、查詢和緩存區(qū)的數(shù)據(jù),你可以通過(guò)以下方法獲得實(shí)體、集合、查詢和緩存區(qū)列表: getQueries()getEntityNames()getCollectionRoleNames()getSecondLevelCacheRegionNames()

    Hibernate程序性能優(yōu)化的考慮要點(diǎn)
    MENGCHUCHEN

    本文依照HIBERNATE幫助文檔,一些網(wǎng)絡(luò)書(shū)籍及項(xiàng)目經(jīng)驗(yàn)整理而成,只提供要點(diǎn)和思路,具體做法可以留言探討,或是找一些更詳細(xì)更有針對(duì)性的資料。

    初用HIBERNATE的人也許都遇到過(guò)性能問(wèn)題,實(shí)現(xiàn)同一功能,用HIBERNATE與用JDBC性能相差十幾倍很正常,如果不及早調(diào)整,很可能影響整個(gè)項(xiàng)目的進(jìn)度。

    大體上,對(duì)于HIBERNATE性能調(diào)優(yōu)的主要考慮點(diǎn)如下:

    • 數(shù)據(jù)庫(kù)設(shè)計(jì)調(diào)整
    • HQL優(yōu)化
    • API的正確使用(如根據(jù)不同的業(yè)務(wù)類型選用不同的集合及查詢API)
    • 主配置參數(shù)(日志,查詢緩存,fetch_size, batch_size等)
    • 映射文件優(yōu)化(ID生成策略,二級(jí)緩存,延遲加載,關(guān)聯(lián)優(yōu)化)
    • 一級(jí)緩存的管理
    • 針對(duì)二級(jí)緩存,還有許多特有的策略
    • 事務(wù)控制策略。

    1、 數(shù)據(jù)庫(kù)設(shè)計(jì)

    a) 降低關(guān)聯(lián)的復(fù)雜性

    b) 盡量不使用聯(lián)合主鍵

    c) ID的生成機(jī)制,不同的數(shù)據(jù)庫(kù)所提供的機(jī)制并不完全一樣

    d) 適當(dāng)?shù)娜哂鄶?shù)據(jù),不過(guò)分追求高范式

    2、 HQL優(yōu)化

    HQL如果拋開(kāi)它同HIBERNATE本身一些緩存機(jī)制的關(guān)聯(lián),HQL的優(yōu)化技巧同普通的SQL優(yōu)化技巧一樣,可以很容易在網(wǎng)上找到一些經(jīng)驗(yàn)之談。

    3、 主配置

    a) 查詢緩存,同下面講的緩存不太一樣,它是針對(duì)HQL語(yǔ)句的緩存,即完全一樣的語(yǔ)句再次執(zhí)行時(shí)可以利用緩存數(shù)據(jù)。但是,查詢緩存在一個(gè)交易系統(tǒng)(數(shù)據(jù)變更頻繁,查詢條件相同的機(jī)率并不大)中可能會(huì)起反作用:它會(huì)白白耗費(fèi)大量的系統(tǒng)資源但卻難以派上用場(chǎng)。

    b) fetch_size,同JDBC的相關(guān)參數(shù)作用類似,參數(shù)并不是越大越好,而應(yīng)根據(jù)業(yè)務(wù)特征去設(shè)置

    c) batch_size同上。

    d) 生產(chǎn)系統(tǒng)中,切記要關(guān)掉SQL語(yǔ)句打印。

    4、 緩存

    a) 數(shù)據(jù)庫(kù)級(jí)緩存:這級(jí)緩存是最高效和安全的,但不同的數(shù)據(jù)庫(kù)可管理的層次并不一樣,比如,在ORACLE中,可以在建表時(shí)指定將整個(gè)表置于緩存當(dāng)中。

    b) SESSION緩存:在一個(gè)HIBERNATE SESSION有效,這級(jí)緩存的可干預(yù)性不強(qiáng),大多于HIBERNATE自動(dòng)管理,但它提供清除緩存的方法,這在大批量增加/更新操作是有效的。比如,同時(shí)增加十萬(wàn)條記錄,按常規(guī)方式進(jìn)行,很可能會(huì)發(fā)現(xiàn)OutofMemeroy的異常,這時(shí)可能需要手動(dòng)清除這一級(jí)緩存:Session.evict以及Session.clear

    c) 應(yīng)用緩存:在一個(gè)SESSIONFACTORY中有效,因此也是優(yōu)化的重中之重,因此,各類策略也考慮的較多,在將數(shù)據(jù)放入這一級(jí)緩存之前,需要考慮一些前提條件:

    i. 數(shù)據(jù)不會(huì)被第三方修改(比如,是否有另一個(gè)應(yīng)用也在修改這些數(shù)據(jù)?)

    ii. 數(shù)據(jù)不會(huì)太大

    iii. 數(shù)據(jù)不會(huì)頻繁更新(否則使用CACHE可能適得其反)

    iv. 數(shù)據(jù)會(huì)被頻繁查詢

    v. 數(shù)據(jù)不是關(guān)鍵數(shù)據(jù)(如涉及錢,安全等方面的問(wèn)題)。

    緩存有幾種形式,可以在映射文件中配置:read-only(只讀,適用于很少變更的靜態(tài)數(shù)據(jù)/歷史數(shù)據(jù)),nonstrict-read-write,read-write(比較普遍的形式,效率一般),transactional(JTA中,且支持的緩存產(chǎn)品較少)

    d) 分布式緩存:同c)的配置一樣,只是緩存產(chǎn)品的選用不同,在目前的HIBERNATE中可供選擇的不多,oscache, jboss cache,目前的大多數(shù)項(xiàng)目,對(duì)它們的用于集群的使用(特別是關(guān)鍵交易系統(tǒng))都持保守態(tài)度。在集群環(huán)境中,只利用數(shù)據(jù)庫(kù)級(jí)的緩存是最安全的。

    5、 延遲加載

    a) 實(shí)體延遲加載:通過(guò)使用動(dòng)態(tài)代理實(shí)現(xiàn)

    b) 集合延遲加載:通過(guò)實(shí)現(xiàn)自有的SET/LIST,HIBERNATE提供了這方面的支持

    c) 屬性延遲加載:

    6、 方法選用

    a) 完成同樣一件事,HIBERNATE提供了可供選擇的一些方式,但具體使用什么方式,可能用性能/代碼都會(huì)有影響。顯示,一次返回十萬(wàn)條記錄(List/Set/Bag/Map等)進(jìn)行處理,很可能導(dǎo)致內(nèi)存不夠的問(wèn)題,而如果用基于游標(biāo)(ScrollableResults)或Iterator的結(jié)果集,則不存在這樣的問(wèn)題。

    b) Session的load/get方法,前者會(huì)使用二級(jí)緩存,而后者則不使用。

    c) Query和list/iterator,如果去仔細(xì)研究一下它們,你可能會(huì)發(fā)現(xiàn)很多有意思的情況,二者主要區(qū)別(如果使用了Spring,在HibernateTemplate中對(duì)應(yīng)find,iterator方法):

    i. list只能利用查詢緩存(但在交易系統(tǒng)中查詢緩存作用不大),無(wú)法利用二級(jí)緩存中的單個(gè)實(shí)體,但list查出的對(duì)象會(huì)寫入二級(jí)緩存,但它一般只生成較少的執(zhí)行SQL語(yǔ)句,很多情況就是一條(無(wú)關(guān)聯(lián))。

    ii. iterator則可以利用二級(jí)緩存,對(duì)于一條查詢語(yǔ)句,它會(huì)先從數(shù)據(jù)庫(kù)中找出所有符合條件的記錄的ID,再通過(guò)ID去緩存找,對(duì)于緩存中沒(méi)有的記錄,再構(gòu)造語(yǔ)句從數(shù)據(jù)庫(kù)中查出,因此很容易知道,如果緩存中沒(méi)有任何符合條件的記錄,使用iterator會(huì)產(chǎn)生N+1條SQL語(yǔ)句(N為符合條件的記錄數(shù))

    iii. 通過(guò)iterator,配合緩存管理API,在海量數(shù)據(jù)查詢中可以很好的解決內(nèi)存問(wèn)題,如:

     while(it.hasNext()){

      YouObject object = (YouObject)it.next();

      session.evict(youObject);

      sessionFactory.evice(YouObject.class, youObject.getId());

      }

    如果用list方法,很可能就出OutofMemory錯(cuò)誤了。

    iv. 通過(guò)上面的說(shuō)明,我想你應(yīng)該知道如何去使用這兩個(gè)方法了。

    7、 集合的選用

    在HIBERNATE 3.1文檔的“19.5. Understanding Collection performance”中有詳細(xì)的說(shuō)明。

    8、 事務(wù)控制

    事務(wù)方面對(duì)性能有影響的主要包括:事務(wù)方式的選用,事務(wù)隔離級(jí)別以及鎖的選用

    a) 事務(wù)方式選用:如果不涉及多個(gè)事務(wù)管理器事務(wù)的話,不需要使用JTA,只有JDBC的事務(wù)控制就可以。

    b) 事務(wù)隔離級(jí)別:參見(jiàn)標(biāo)準(zhǔn)的SQL事務(wù)隔離級(jí)別

    c) 鎖的選用:悲觀鎖(一般由具體的事務(wù)管理器實(shí)現(xiàn)),對(duì)于長(zhǎng)事務(wù)效率低,但安全。樂(lè)觀鎖(一般在應(yīng)用級(jí)別實(shí)現(xiàn)),如在HIBERNATE中可以定義VERSION字段,顯然,如果有多個(gè)應(yīng)用操作數(shù)據(jù),且這些應(yīng)用不是用同一種樂(lè)觀鎖機(jī)制,則樂(lè)觀鎖會(huì)失效。因此,針對(duì)不同的數(shù)據(jù)應(yīng)有不同的策略,同前面許多情況一樣,很多時(shí)候我們是在效率與安全/準(zhǔn)確性上找一個(gè)平衡點(diǎn),無(wú)論如何,優(yōu)化都不是一個(gè)純技術(shù)的問(wèn)題,你應(yīng)該對(duì)你的應(yīng)用和業(yè)務(wù)特征有足夠的了解。

    9、 批量操作

    即使是使用JDBC,在進(jìn)行大批數(shù)據(jù)更新時(shí),BATCH與不使用BATCH有效率上也有很大的差別。我們可以通過(guò)設(shè)置batch_size來(lái)讓其支持批量操作。

    舉個(gè)例子,要批量刪除某表中的對(duì)象,如“delete Account”,打出來(lái)的語(yǔ)句,會(huì)發(fā)現(xiàn)HIBERNATE找出了所有ACCOUNT的ID,再進(jìn)行刪除,這主要是為了維護(hù)二級(jí)緩存,這樣效率肯定高不了,在后續(xù)的版本中增加了bulk delete/update,但這也無(wú)法解決緩存的維護(hù)問(wèn)題。也就是說(shuō),由于有了二級(jí)緩存的維護(hù)問(wèn)題,HIBERNATE的批量操作效率并不盡如人意!

    從前面許多要點(diǎn)可以看出,很多時(shí)候我們是在效率與安全/準(zhǔn)確性上找一個(gè)平衡點(diǎn),無(wú)論如何,優(yōu)化都不是一個(gè)純技術(shù)的問(wèn)題,你應(yīng)該對(duì)你的應(yīng)用和業(yè)務(wù)特征有足夠的了解,一般的,優(yōu)化方案應(yīng)在架構(gòu)設(shè)計(jì)期就基本確定,否則可能導(dǎo)致沒(méi)必要的返工,致使項(xiàng)目延期,而作為架構(gòu)師和項(xiàng)目經(jīng)理,還要面對(duì)開(kāi)發(fā)人員可能的抱怨,必竟,我們對(duì)用戶需求更改的控制力不大,但技術(shù)/架構(gòu)風(fēng)險(xiǎn)是應(yīng)該在初期意識(shí)到并制定好相關(guān)的對(duì)策。

    還有一點(diǎn)要注意,應(yīng)用層的緩存只是錦上添花,永遠(yuǎn)不要把它當(dāng)救命稻草,應(yīng)用的根基(數(shù)據(jù)庫(kù)設(shè)計(jì),算法,高效的操作語(yǔ)句,恰當(dāng)API的選擇等)才是最重要的。

    posted on 2007-05-15 15:58 ???MengChuChen 閱讀(3330) 評(píng)論(2)  編輯  收藏 所屬分類: hibernate

    FeedBack:
    # re: HIbernate提升性能
    2008-08-30 17:05 | sqxy
    b) Session的load/get方法,前者會(huì)使用二級(jí)緩存,而后者則不使用。
    =============================

    確定?  回復(fù)  更多評(píng)論
      
    # re: HIbernate提升性能
    2008-08-30 17:07 | sqxy
    據(jù)我試驗(yàn),load和get的區(qū)別是在查詢不到內(nèi)容時(shí),一個(gè)拋異常,一個(gè)返回null。
    還有就是,load延遲加載,get是即時(shí)加載的。
    至于2級(jí)緩存,我做過(guò)試驗(yàn),get同樣利用到了二級(jí)緩存。  回復(fù)  更多評(píng)論
      
    主站蜘蛛池模板: 国产免费女女脚奴视频网| 野花香高清在线观看视频播放免费 | 亚洲一区中文字幕在线电影网| 亚洲人xxx日本人18| 亚洲国产成人无码AV在线| 色婷婷精品免费视频| 三根一起会坏掉的好痛免费三级全黄的视频在线观看 | 羞羞视频免费网站在线看| 久久国产精品成人免费| 免费人成在线观看69式小视频| 免费激情视频网站| 亚洲日韩国产一区二区三区| 亚洲av日韩av天堂影片精品| 亚洲伊人精品综合在合线| 精品在线视频免费| 麻豆精品不卡国产免费看| 国产1024精品视频专区免费| 国产一区二区三区免费看| 国产亚洲综合色就色| 亚洲1区1区3区4区产品乱码芒果| 在线观看亚洲精品专区| 成人性生交大片免费看中文| 18勿入网站免费永久| 国产成人精品日本亚洲专区| 亚洲综合无码一区二区三区| 亚洲欧美aⅴ在线资源| 久久er国产精品免费观看8| 成人黄色免费网址| 亚洲成人影院在线观看| 亚洲国产高清在线精品一区| 欧亚一级毛片免费看| 亚洲视频在线免费看| 又粗又硬免费毛片| 7777久久亚洲中文字幕蜜桃| 国产亚洲视频在线| 131美女爱做免费毛片| 少妇亚洲免费精品| 亚洲成人黄色网址| 丝袜捆绑调教视频免费区| 在线观看免费a∨网站| 亚洲AV日韩AV鸥美在线观看|