hibernate 延迟加载(转载)
http://blog.csdn.net/xc635960736/article/details/7049863(未找到原始的引文)
Hibernae 的延遲加載是一個(gè)非常常用的技術(shù),實(shí)體的集合屬性默認(rèn)會(huì)被延遲加載,實(shí)體所關(guān)聯(lián)的實(shí)體默認(rèn)也會(huì)被延遲加載。Hibernate 通過(guò)這種延遲加載來(lái)降低系統(tǒng)的內(nèi)存開(kāi)銷,從而保證 Hibernate 的運(yùn)行性能。
下面先來(lái)剖析 Hibernate 延遲加載的“秘密”。
集合屬性的延遲加載
當(dāng) Hibernate 從數(shù)據(jù)庫(kù)中初始化某個(gè)持久化實(shí)體時(shí),該實(shí)體的集合屬性是否隨持久化類一起初始化呢?如果集合屬性里包含十萬(wàn),甚至百萬(wàn)的記錄,在初始化持久化實(shí)體的同時(shí), 完成所有集合屬性的抓取,將導(dǎo)致性能急劇下降。完全有可能系統(tǒng)只需要使用持久化類集合屬性中的部分記錄,而完全不是集合屬性的全部,這樣,沒(méi)有必要一次加 載所有的集合屬性。
對(duì)于集合屬性,通常推薦使用延遲加載策略。所謂延遲加載就是等系統(tǒng)需要使用集合屬性時(shí)才從數(shù)據(jù)庫(kù)裝載關(guān)聯(lián)的數(shù)據(jù)。
例如下面 Person 類持有一個(gè)集合屬性,該集合屬性里的元素的類型為 Address,該 Person 類的代碼片段如下:
清單 1. Person.java
| ?????????????????????????????? ? ?public class Person ?{ ?// 標(biāo)識(shí)屬性 private Integer id; ?// Person 的 name 屬性 private String name; ?// 保留 Person 的 age 屬性 private int age; ?// 使用 Set 來(lái)保存集合屬性 private Set<Address> addresses = new HashSet<Address>(); ?// 下面省略了各屬性的 setter 和 getter 方法 ... ?} |
?
為了讓 Hibernate 能管理該持久化類的集合屬性,程序?yàn)樵摮志没愄峁┤缦掠成湮募?#xff1a;
清單 2. Person.hbm.xml
| ?????????????????????????????? ? ?<?xml version="1.0" encoding="GBK"?> ?<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> ?<hibernate-mapping package="org.crazyit.app.domain"> ?<!-- 映射 Person 持久化類 --> ?<class name="Person" table="person_inf"> ?<!-- 映射標(biāo)識(shí)屬性 id --> ?<id name="id" column="person_id"> ?<!-- 定義主鍵生成器策略 --> ?<generator class="identity"/> ?</id> ?<!-- 用于映射普通屬性 --> ?<property name="name" type="string"/> ?<property name="age" type="int"/> ?<!-- 映射集合屬性? --> ?<set name="addresses" table="person_address" lazy="true"> ?<!-- 指定關(guān)聯(lián)的外鍵列 --> ?<key column="person_id"/> ?<composite-element class="Address"> ?<!-- 映射普通屬性 detail --> ?<property name="detail"/> ?<!-- 映射普通屬性 zip --> ?<property name="zip"/> ?</composite-element> ?</set> ?</class> ?</hibernate-mapping> |
?
從上面映射文件的代碼可以看出,Person 的集合屬性中的 Address 類只是一個(gè)普通的 POJO。該 Address 類里包含 detail、zip 兩個(gè)屬性。由于 Address 類代碼非常簡(jiǎn)單,故此處不再給出該類的代碼。
上面映射文件中 <set.../> 元素里的代碼指定了 lazy="true"(對(duì)于 <set.../> 元素來(lái)說(shuō),lazy="true"是默認(rèn)值),它指定 Hibernate 會(huì)延遲加載集合屬性里 Address 對(duì)象。
例如通過(guò)如下代碼來(lái)加載 ID 為 1 的 Person 實(shí)體:
| ?Session session = sf.getCurrentSession(); ?Transaction tx = session.beginTransaction(); ?Person p = (Person) session.get(Person.class, 1);? //<1> ?System.out.println(p.getName()); |
?
上面代碼只是需要訪問(wèn) ID 為 1 的 Person 實(shí)體,并不想訪問(wèn)這個(gè) Person 實(shí)體所關(guān)聯(lián)的 Address 對(duì)象。此時(shí)有兩種情況:
如果不延遲加載,Hibernate 就會(huì)在加載 Person 實(shí)體對(duì)應(yīng)的數(shù)據(jù)記錄時(shí)立即抓取它關(guān)聯(lián)的 Address 對(duì)象。
如果采用延遲加載,Hibernate 就只加載 Person 實(shí)體對(duì)應(yīng)的數(shù)據(jù)記錄。
很明顯,第二種做法既能減少與數(shù)據(jù)庫(kù)的交互,而且避免了裝載 Address 實(shí)體帶來(lái)的內(nèi)存開(kāi)銷——這也是 Hibernate 默認(rèn)啟用延遲加載的原因。
現(xiàn)在的問(wèn)題是,延遲加載到底是如何實(shí)現(xiàn)的呢? Hibernate 在加載 Person 實(shí)體時(shí),Person 實(shí)體的 addresses 屬性值是什么呢?
為了解決這個(gè)問(wèn)題,我們?cè)?<1>號(hào)代碼處設(shè)置一個(gè)斷點(diǎn),在 Eclipse 中進(jìn)行 Debug,此時(shí)可以看到 Eclipse 的 Console 窗口有如圖 1 所示的輸出:
圖 1. 延遲加載集合屬性的 Console 輸出
?
正如圖 1 輸出所看到的,此時(shí) Hibernate 只從 Person 實(shí)體對(duì)應(yīng)的數(shù)據(jù)表中抓取數(shù)據(jù),并未從 Address 對(duì)象對(duì)應(yīng)的數(shù)據(jù)表中抓取數(shù)據(jù),這就是延遲加載。
那么 Person 實(shí)體的 addresses 屬性是什么呢?此時(shí)可以從 Eclipse 的 Variables 窗口看到如圖 2 所示的結(jié)果:
圖 2. 延遲加載的集合屬性值
?
從圖 2 的方框里的內(nèi)容可以看出,這個(gè) addresses 屬性并不是我們熟悉的 HashSet、TreeSet 等實(shí)現(xiàn)類,而是一個(gè) PersistentSet 實(shí)現(xiàn)類,這是 Hibernate 為 Set 接口提供的一個(gè)實(shí)現(xiàn)類。
PersistentSet 集合對(duì)象并未真正抓取底層數(shù)據(jù)表的數(shù)據(jù),因此自然也無(wú)法真正去初始化集合里的 Address 對(duì)象。不過(guò) PersistentSet 集合里持有一個(gè) session 屬性,這個(gè) session 屬性就是 Hibernate Session,當(dāng)程序需要訪問(wèn) PersistentSet 集合元素時(shí),PersistentSet 就會(huì)利用這個(gè) session 屬性去抓取實(shí)際的 Address 對(duì)象對(duì)應(yīng)的數(shù)據(jù)記錄。
那么到底抓取那些 Address 實(shí)體對(duì)應(yīng)的數(shù)據(jù)記錄呢?這也難不倒 PersistentSet,因?yàn)?PersistentSet 集合里還有一個(gè) owner 屬性,該屬性就說(shuō)明了 Address 對(duì)象所屬的 Person 實(shí)體,Hibernate 就會(huì)去查找 Address 對(duì)應(yīng)數(shù)據(jù)表中外鍵值參照到該 Person 實(shí)體的數(shù)據(jù)。
例如我們單擊圖 2 所示窗口中 addresses 行,也就是告訴 Eclipse 要調(diào)試、輸出 addresses 屬性,這就是要訪問(wèn) addresses 屬性了,此時(shí)就可以在 Eclipse 的 Console 窗口看到輸出如下 SQL 語(yǔ)句:
| ??? select ????????addresses0_.person_id as person1_0_0_, ????????addresses0_.detail as detail0_, ????????addresses0_.zip as zip0_ ????from ????????person_address addresses0_ ????where ????????addresses0_.person_id=? |
?
這就是 PersistentSet 集合跟據(jù) owner 屬性去抓取特定 Address 記錄的 SQL 語(yǔ)句。此時(shí)可以從 Eclipse 的 Variables 窗口看到圖 3 所示的輸出:
圖 3. 已加載的集合屬性值
?
從圖 3 可以看出,此時(shí)的 addresses 屬性已經(jīng)被初始化了,集合里包含了 2 個(gè) Address 對(duì)象,這正是 Person 實(shí)體所關(guān)聯(lián)的兩個(gè) Address 對(duì)象。
通過(guò)上面介紹可以看出,Hibernate 對(duì)于 Set 屬性延遲加載關(guān)鍵就在于 PersistentSet 實(shí)現(xiàn)類。在延遲加載時(shí),開(kāi)始 PersistentSet 集合里并不持有任何元素。但 PersistentSet 會(huì)持有一個(gè) Hibernate Session,它可以保證當(dāng)程序需要訪問(wèn)該集合時(shí)“立即”去加載數(shù)據(jù)記錄,并裝入集合元素。
與 PersistentSet 實(shí)現(xiàn)類類似的是,Hibernate 還提供了 PersistentList、PersistentMap、PersistentSortedMap、PersistentSortedSet 等實(shí)現(xiàn)類,它們的功能與 PersistentSet 的功能大致類似。
熟悉 Hibernate 集合屬性讀者應(yīng)該記得:Hibernate 要求聲明集合屬性只能用 Set、List、Map、SortedSet、SortedMap 等接口,而不能用 HashSet、ArrayList、HashMap、TreeSet、TreeMap 等實(shí)現(xiàn)類,其原因就是因?yàn)?Hibernate 需要對(duì)集合屬性進(jìn)行延遲加載,而 Hibernate 的延遲加載是依靠 PersistentSet、PersistentList、PersistentMap、PersistentSortedMap、 PersistentSortedSet 來(lái)完成的——也就是說(shuō),Hibernate 底層需要使用自己的集合實(shí)現(xiàn)類來(lái)完成延遲加載,因此它要求開(kāi)發(fā)者必須用集合接口、而不是集合實(shí)現(xiàn)類來(lái)聲明集合屬性。
Hibernate 對(duì)集合屬性默認(rèn)采用延遲加載,在某些特殊的情況下,為 <set.../>、<list.../>、<map.../> 等元素設(shè)置 lazy="false"屬性來(lái)取消延遲加載。
?
回頁(yè)首
關(guān)聯(lián)實(shí)體的延遲加載
默認(rèn)情況下,Hibernate 也會(huì)采用延遲加載來(lái)加載關(guān)聯(lián)實(shí)體,不管是一對(duì)多關(guān)聯(lián)、還是一對(duì)一關(guān)聯(lián)、多對(duì)多關(guān)聯(lián),Hibernate 默認(rèn)都會(huì)采用延遲加載。
對(duì)于關(guān)聯(lián)實(shí)體,可以將其分為兩種情況:
關(guān)聯(lián)實(shí)體是多個(gè)實(shí)體時(shí)(包括一對(duì)多、多對(duì)多):此時(shí)關(guān)聯(lián)實(shí)體將以集合的形式存在,Hibernate 將使用 PersistentSet、PersistentList、PersistentMap、PersistentSortedMap、 PersistentSortedSet 等集合來(lái)管理延遲加載的實(shí)體。這就是前面所介紹的情形。
關(guān)聯(lián)實(shí)體是單個(gè)實(shí)體時(shí)(包括一對(duì)一、多對(duì)一):當(dāng) Hibernate 加載某個(gè)實(shí)體時(shí),延遲的關(guān)聯(lián)實(shí)體將是一個(gè)動(dòng)態(tài)生成代理對(duì)象。
當(dāng)關(guān)聯(lián)實(shí)體是單個(gè)實(shí)體時(shí),也就是使用 <many-to-one.../> 或 <one-to-one.../> 映射關(guān)聯(lián)實(shí)體的情形,這兩個(gè)元素也可通過(guò) lazy 屬性來(lái)指定延遲加載。
下面例子把 Address 類也映射成持久化類,此時(shí) Address 類也變成實(shí)體類,Person 實(shí)體與 Address 實(shí)體形成一對(duì)多的雙向關(guān)聯(lián)。此時(shí)的映射文件代碼如下:
清單 3. Person.hbm.xml
| ?????????????????????????????? ? ?<?xml version="1.0" encoding="GBK"?> ?<!-- 指定 Hibernate 的 DTD 信息 --> ?<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> ?<hibernate-mapping package="org.crazyit.app.domain"> ?<!-- 映射 Person 持久化類 --> ?<class name="Person" table="person_inf"> ?<!-- 映射標(biāo)識(shí)屬性 id --> ?<id name="id" column="person_id"> ?<!-- 定義主鍵生成器策略 --> ?<generator class="identity"/> ?</id> ?<!-- 用于映射普通屬性 --> ?<property name="name" type="string"/> ?<property name="age" type="int"/> ?<!-- 映射集合屬性,集合元素是其他持久化實(shí)體 沒(méi)有指定 cascade 屬性,指定不控制關(guān)聯(lián)關(guān)系 --> ?<set name="addresses" inverse="true"> ?<!-- 指定關(guān)聯(lián)的外鍵列 --> ?<key column="person_id"/> ?<!-- 用以映射到關(guān)聯(lián)類屬性 --> ?<one-to-many class="Address"/> ?</set> ?</class> ? <!-- 映射 Address 持久化類 --> ?<class name="Address" table="address_inf"> ?<!-- 映射標(biāo)識(shí)屬性 addressId --> ?<id name="addressId" column="address_id"> ?<!-- 指定主鍵生成器策略 --> ?<generator class="identity"/> ?</id> ?<!-- 映射普通屬性 detail --> ?<property name="detail"/> ?<!-- 映射普通屬性 zip --> ?<property name="zip"/> ?<!-- 必須指定列名為 person_id, 與關(guān)聯(lián)實(shí)體中 key 元素的 column 屬性值相同 --> ?<many-to-one name="person" class="Person" column="person_id" not-null="true"/> ?</class> ?</hibernate-mapping> |
?
接下來(lái)程序通過(guò)如下代碼片段來(lái)加載 ID 為 1 的 Person 實(shí)體:
| ?// 打開(kāi)上下文相關(guān)的 Session ?Session session = sf.getCurrentSession(); ?Transaction tx = session.beginTransaction(); ?Address address = (Address) session.get(Address.class , 1); //<1> ?System.out.println(address.getDetail()); |
?
為了看到 Hibernate 加載 Address 實(shí)體時(shí)對(duì)其關(guān)聯(lián)實(shí)體的處理,我們?cè)?<1>號(hào)代碼處設(shè)置一個(gè)斷點(diǎn),在 Eclipse 中進(jìn)行 Debug,此時(shí)可以看到 Eclipse 的 Console 窗口輸出如下 SQL 語(yǔ)句:
| ??? select ????????address0_.address_id as address1_1_0_, ????????address0_.detail as detail1_0_, ????????address0_.zip as zip1_0_, ????????address0_.person_id as person4_1_0_ ????from ????????address_inf address0_ ????where ????????address0_.address_id=? |
?
從這條 SQL 語(yǔ)句不難看出,Hibernate 加載 Address 實(shí)體對(duì)應(yīng)的數(shù)據(jù)表抓取記錄,并未從 Person 實(shí)體對(duì)應(yīng)的數(shù)據(jù)表中抓取記錄,這是延遲加載發(fā)揮了作用。
從 Eclipse 的 Variables 窗口看到如圖 4 所示的輸出:
圖 4. 延遲加載的實(shí)體
?
從圖 4 可以清楚地看到,此時(shí) Address 實(shí)體所關(guān)聯(lián)的 Person 實(shí)體并不是 Person 對(duì)象,而是一個(gè) Person_$$_javassist_0 類的實(shí)例,這個(gè)類是 Hibernate 使用 Javassist 項(xiàng)目動(dòng)態(tài)生成的代理類——當(dāng) Hibernate 延遲加載關(guān)聯(lián)實(shí)體時(shí),將會(huì)采用 Javassist 生成一個(gè)動(dòng)態(tài)代理對(duì)象,這個(gè)代理對(duì)象將負(fù)責(zé)代理“暫未加載”的關(guān)聯(lián)實(shí)體。
只要應(yīng)用程序需要使用“暫未加載”的關(guān)聯(lián)實(shí)體,Person_$$_javassist_0 代理對(duì)象會(huì)負(fù)責(zé)去加載真正的關(guān)聯(lián)實(shí)體,并返回實(shí)際的關(guān)聯(lián)實(shí)體——這就是最典型的代理模式。
單擊圖 4 所示 Variables 窗口中的 person 屬性(也就是在調(diào)試模式下強(qiáng)行使用 person 屬性),此時(shí)看到 Eclipse 的 Console 窗口輸出如下的 SQL 語(yǔ)句:
| ??? select ????????person0_.person_id as person1_0_0_, ????????person0_.name as name0_0_, ????????person0_.age as age0_0_ ????from ????????person_inf person0_ ????where ????????person0_.person_id=? |
?
上面 SQL 語(yǔ)句就是去抓取“延遲加載”的關(guān)聯(lián)實(shí)體的語(yǔ)句。此時(shí)可以看到 Variables 窗口輸出圖 5 所示的結(jié)果:
圖 5. 已加載的實(shí)體
?
Hibernate 采用“延遲加載”管理關(guān)聯(lián)實(shí)體的模式,其實(shí)就在加載主實(shí)體時(shí),并未真正去抓取關(guān)聯(lián)實(shí)體對(duì)應(yīng)數(shù)據(jù),而只是動(dòng)態(tài)地生成一個(gè)對(duì)象作為關(guān)聯(lián)實(shí)體的代理。當(dāng)應(yīng)用程序真正需要使用關(guān)聯(lián)實(shí)體時(shí),代理對(duì)象會(huì)負(fù)責(zé)從底層數(shù)據(jù)庫(kù)抓取記錄,并初始化真正的關(guān)聯(lián)實(shí)體。
在 Hibernate 的延遲加載中,客戶端程序開(kāi)始獲取的只是一個(gè)動(dòng)態(tài)生成的代理對(duì)象,而真正的實(shí)體則委托給代理對(duì)象來(lái)管理——這就是典型的代理模式。
?
回頁(yè)首
代理模式
代理模式是一種應(yīng)用非常廣泛的設(shè)計(jì)模式,當(dāng)客戶端代碼需要調(diào)用某個(gè)對(duì)象時(shí),客戶端實(shí)際上也不關(guān)心是否準(zhǔn)確得到該對(duì)象,它只要一個(gè)能提供該功能的對(duì)象即可,此時(shí)我們就可返回該對(duì)象的代理(Proxy)。
在這種設(shè)計(jì)方式下,系統(tǒng)會(huì)為某個(gè)對(duì)象提供一個(gè)代理對(duì)象,并由代理對(duì)象控制對(duì)源對(duì)象的引用。代理就是一個(gè) Java 對(duì)象代表另一個(gè) Java 對(duì)象來(lái)采取行動(dòng)。在某些情況下,客戶端代碼不想或不能夠直接調(diào)用被調(diào)用者,代理對(duì)象可以在客戶和目標(biāo)對(duì)象之間起到中介的作用。
對(duì)客戶端而言,它不能分辨出代理對(duì)象與真實(shí)對(duì)象的區(qū)別,它也無(wú)須分辨代理對(duì)象和真實(shí)對(duì)象的區(qū)別。客戶端代碼并不知道真正的被代理對(duì)象,客戶端代碼面向接口編程,它僅僅持有一個(gè)被代理對(duì)象的接口。
總而言之,只要客戶端代碼不能或不想直接訪問(wèn)被調(diào)用對(duì)象——這種情況有很多原因,比如需要?jiǎng)?chuàng)建一個(gè)系統(tǒng)開(kāi)銷很大的對(duì)象,或者被調(diào)用對(duì)象在遠(yuǎn)程主機(jī)上,或者目標(biāo)對(duì)象的功能還不足以滿足需求……,而是額外創(chuàng)建一個(gè)代理對(duì)象返回給客戶端使用,那么這種設(shè)計(jì)方式就是代理模式。
下面示范一個(gè)簡(jiǎn)單的代理模式,程序首先提供了一個(gè) Image 接口,代表大圖片對(duì)象所實(shí)現(xiàn)的接口,該接口代碼如下:
清單 3. Image.java
| ?????????????????????????????? ? ?public interface Image ?{ ?void show(); ?} |
?
該接口提供了一個(gè)實(shí)現(xiàn)類,該實(shí)現(xiàn)類模擬了一個(gè)大圖片對(duì)象,該實(shí)現(xiàn)類的構(gòu)造器使用 Thread.sleep() 方法來(lái)暫停 3s。下面是該 BigImage 的程序代碼。
清單 4. BigImage.java
| ?????????????????????????????? ? ?// 使用該 BigImage 模擬一個(gè)很大圖片 public class BigImage implements Image ?{ ?public BigImage() ?{ ?try ?{ ?// 程序暫停 3s 模式模擬系統(tǒng)開(kāi)銷 ?????????????????????????????? ?Thread.sleep(3000); ?System.out.println("圖片裝載成功 ..."); ?} ?catch (InterruptedException ex) ?{ ?ex.printStackTrace(); ?} ?} ?// 實(shí)現(xiàn) Image 里的 show() 方法 public void show() ?{ ?System.out.println("繪制實(shí)際的大圖片"); ?} ?} |
?
上面的程序代碼暫停了 3s,這表明創(chuàng)建一個(gè) BigImage 對(duì)象需要 3s 的時(shí)間開(kāi)銷——程序使用這種延遲來(lái)模擬裝載此圖片所導(dǎo)致的系統(tǒng)開(kāi)銷。如果不采用代理模式,當(dāng)程序中創(chuàng)建 BigImage 時(shí),系統(tǒng)將會(huì)產(chǎn)生 3s 的延遲。為了避免這種延遲,程序?yàn)?BigImage 對(duì)象提供一個(gè)代理對(duì)象,BigImage 類的代理類如下所示。
清單 5. ImageProxy.java
| ?????????????????????????????? ? ?public class ImageProxy implements Image ?{ ?// 組合一個(gè) image 實(shí)例,作為被代理的對(duì)象 private Image image; ?// 使用抽象實(shí)體來(lái)初始化代理對(duì)象 public ImageProxy(Image image) ?{ ?this.image = image; ?} ?/** ?* 重寫(xiě) Image 接口的 show() 方法 * 該方法用于控制對(duì)被代理對(duì)象的訪問(wèn), * 并根據(jù)需要負(fù)責(zé)創(chuàng)建和刪除被代理對(duì)象 */ ?public void show() ?{ ?// 只有當(dāng)真正需要調(diào)用 image 的 show 方法時(shí)才創(chuàng)建被代理對(duì)象 if (image == null) ?{ ??image = new BigImage(); ??} ?image.show(); ?} ?} |
?
上面的 ImageProxy 代理類實(shí)現(xiàn)了與 BigImage 相同的 show() 方法,這使得客戶端代碼獲取到該代理對(duì)象之后,可以將該代理對(duì)象當(dāng)成 BigImage 來(lái)使用。
在 ImageProxy 類的 show() 方法中增加了控制邏輯,這段控制邏輯用于控制當(dāng)系統(tǒng)真正調(diào)用 image 的 show() 時(shí),才會(huì)真正創(chuàng)建被代理的 BigImage 對(duì)象。下面程序需要使用 BigImage 對(duì)象,但程序并不是直接返回 BigImage 實(shí)例,而是先返回 BigImage 的代理對(duì)象,如下面程序所示。
清單 6. BigImageTest.java
| ?????????????????????????????? ? ?public class BigImageTest ?{ ?public static void main(String[] args) ?{ ?long start = System.currentTimeMillis(); ?// 程序返回一個(gè) Image 對(duì)象,該對(duì)象只是 BigImage 的代理對(duì)象 Image image = new ImageProxy(null); ?System.out.println("系統(tǒng)得到 Image 對(duì)象的時(shí)間開(kāi)銷 :" + ?(System.currentTimeMillis() - start)); ?// 只有當(dāng)實(shí)際調(diào)用 image 代理的 show() 方法時(shí),程序才會(huì)真正創(chuàng)建被代理對(duì)象。 image.show(); ?} ?} |
?
上面程序初始化 image 非常快,因?yàn)槌绦虿⑽凑嬲齽?chuàng)建 BigImage 對(duì)象,只是得到了 ImageProxy 代理對(duì)象——直到程序調(diào)用 image.show() 方法時(shí),程序需要真正調(diào)用 BigImage 對(duì)象的 show() 方法,程序此時(shí)才真正創(chuàng)建 BigImage 對(duì)象。運(yùn)行上面程序,看到如圖 6 所示的結(jié)果。
圖 6. 使用代理模式提高性能
?
看到如圖 6 所示的運(yùn)行結(jié)果,讀者應(yīng)該能認(rèn)同:使用代理模式提高了獲取 Image 對(duì)象的系統(tǒng)性能。但可能有讀者會(huì)提出疑問(wèn):程序調(diào)用 ImageProxy 對(duì)象的 show() 方法時(shí)一樣需要?jiǎng)?chuàng)建 BigImage 對(duì)象啊,系統(tǒng)開(kāi)銷并未真正減少啊?只是這種系統(tǒng)開(kāi)銷延遲了而已啊?
我們可以從如下兩個(gè)角度來(lái)回答這個(gè)問(wèn)題:
把創(chuàng)建 BigImage 推遲到真正需要它時(shí)才創(chuàng)建,這樣能保證前面程序運(yùn)行的流暢性,而且能減少 BigImage 在內(nèi)存中的存活時(shí)間,從宏觀上節(jié)省了系統(tǒng)的內(nèi)存開(kāi)銷。
有些情況下,也許程序永遠(yuǎn)不會(huì)真正調(diào)用 ImageProxy 對(duì)象的 show() 方法——意味著系統(tǒng)根本無(wú)須創(chuàng)建 BigImage 對(duì)象。在這種情形下,使用代理模式可以顯著地提高系統(tǒng)運(yùn)行性能。
與此完全類似的是,Hibernate 也是通過(guò)代理模式來(lái)“推遲”加載關(guān)聯(lián)實(shí)體的時(shí)間,如果程序并不需要訪問(wèn)關(guān)聯(lián)實(shí)體,那程序就不會(huì)去抓取關(guān)聯(lián)實(shí)體了,這樣既可以節(jié)省系統(tǒng)的內(nèi)存開(kāi)銷,也可以縮短 Hibernate 加載實(shí)體的時(shí)間。
?
回頁(yè)首
小結(jié)
Hibernate 的延遲加載(lazy load)本質(zhì)上就是代理模式的應(yīng)用,我們?cè)谶^(guò)去的歲月里就經(jīng)常通過(guò)代理模式來(lái)降低系統(tǒng)的內(nèi)存開(kāi)銷、提升應(yīng)用的運(yùn)行性能。Hibernate 充分利用了代理模式的這種優(yōu)勢(shì),并結(jié)合了 Javassist 或 CGLIB 來(lái)動(dòng)態(tài)地生成代理對(duì)象,這更加增加了代理模式的靈活性,Hibernate 給這種用法一個(gè)新名稱:延遲加載。無(wú)論怎樣,充分分析、了解這些開(kāi)源框架的實(shí)現(xiàn)可以更好的感受經(jīng)典設(shè)計(jì)模式的優(yōu)勢(shì)所在。
?
轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/archive/2013/04/02/2995376.html
總結(jié)
以上是生活随笔為你收集整理的hibernate 延迟加载(转载)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Hibernate的dynamic-in
- 下一篇: 主要几种通信协议的性能比较(转载)