默認情況下,Hibernate也會採用延遲加載來加載關聯實體,不管是一對多關聯、還是一對一關聯、多對多關聯,Hibernate 默認都會採用延遲加載。
對於關聯實體,可以將其分爲兩種情況:
- 關聯實體是多個實體時(包括一對多、多對多):此時關聯實體將以集合的形式存在,Hibernate 將使用 PersistentSet、PersistentList、PersistentMap、PersistentSortedMap、PersistentSortedSet 等集合來管理延遲加載的實體。這就是前面所介紹的情形。
- 關聯實體是單個實體時(包括一對一、多對一):當 Hibernate 加載某個實體時,延遲的關聯實體將是一個動態生成代理對象。
當關聯實體是單個實體時,也就是使用<many-to-one.../> 或 <one-to-one.../> 映射關聯實體的情形,這兩個元素也可通過 lazy屬性來指定延遲加載。
下面例子把Address 類也映射成持久化類,此時 Address 類也變成實體類,Person 實體與 Address實體形成一對多的雙向關聯。此時的映射文件代碼如下:
Person.hbm.xml
<hibernate-mappingpackage="org.crazyit.app.domain">
<classname="Person" table="person_inf">
<idname="id" column="person_id">
<generatorclass="identity" />
</id>
<propertyname="name" type="string" />
<propertyname="age" type="int" />
<!-- 映射集合屬性,集合元素是其他持久化實體 沒有指定 cascade 屬性,指定不控制關聯關係 -->
<setname="addresses" inverse="true">
<!-- 指定關聯的外鍵列-->
<keycolumn="person_id" />
<!-- 用以映射到關聯類屬性 -->
<one-to-manyclass="Address" />
</set>
</class>
<!-- 映射Address 持久化類 -->
<classname="Address" table="address_inf">
<!-- 映射標識屬性addressId -->
<idname="addressId" column="address_id">
<!-- 指定主鍵生成器策略 -->
<generatorclass="identity" />
</id>
<!-- 映射普通屬性detail -->
<propertyname="detail" />
<!-- 映射普通屬性zip -->
<propertyname="zip" />
<!-- 必須指定列名爲person_id, 與關聯實體中 key 元素的column 屬性值相同 -->
<many-to-onename="person" class="Person" column="person_id"
not-null="true"/>
</class>
</hibernate-mapping>
接下來程序通過如下代碼片段來加載ID 爲 1 的 Person 實體:
//
打開上下文相關的 Session
Session session = sessionFactory.getCurrentSession();
Transaction tx =session.beginTransaction();
Address address = (Address)session.get(Address.class , 1); //<1>
System.out.println(address.getDetail());
爲了看到 Hibernate 加載 Address 實體時對其關聯實體的處理,我們在 <1>號代碼處設置一個斷點,在 Eclipse 中進行 Debug,此時可以看到 Eclipse 的 Console 窗口輸出如下 SQL 語句:
select
address0_.address_id asaddress1_1_0_,
address0_.detail as detail1_0_,
address0_.zip as zip1_0_,
address0_.person_id asperson4_1_0_
from
address_inf address0_
where
address0_.address_id=?
從這條SQL 語句不難看出,Hibernate 加載 Address 實體對應的數據表抓取記錄,並未從 Person實體對應的數據表中抓取記錄,這是延遲加載發揮了作用。
從Eclipse 的 Variables 窗口看到如圖所示的輸出:
延遲加載的實體
從圖可以清楚地看到,此時Address 實體所關聯的 Person 實體並不是 Person 對象,而是一個 Person_$$_javassist_0 類的實例,這個類是Hibernate 使用 Javassist 項目動態生成的代理類——當 Hibernate 延遲加載關聯實體時,將會採用 Javassist生成一個動態代理對象,這個代理對象將負責代理“暫未加載”的關聯實體。
只要應用程序需要使用“暫未加載”的關聯實體,Person_$$_javassist_0代理對象會負責去加載真正的關聯實體,並返回實際的關聯實體——這就是最典型的代理模式。
單擊圖所示Variables 窗口中的 person 屬性(也就是在調試模式下強行使用 person 屬性),此時看到 Eclipse 的 Console窗口輸出如下的 SQL 語句:
select
person0_.person_id asperson1_0_0_,
person0_.name as name0_0_,
person0_.age as age0_0_
from
person_inf person0_
where
person0_.person_id=?
上面SQL 語句就是去抓取“延遲加載”的關聯實體的語句。此時可以看到 Variables 窗口輸出圖所示的結果:
已加載的實體
Hibernate採用“延遲加載”管理關聯實體的模式,其實就在加載主實體時,並未真正去抓取關聯實體對應數據,而只是動態地生成一個對象作爲關聯實體的代理。當應用程序真正需要使用關聯實體時,代理對象會負責從底層數據庫抓取記錄,並初始化真正的關聯實體。
在Hibernate 的延遲加載中,客戶端程序開始獲取的只是一個動態生成的代理對象,而真正的實體則委託給代理對象來管理——這就是典型的代理模式。