這就是 PersistentSet 集合跟據 owner 屬性去抓取特定 Address 記錄的 SQL 語句。此時可以從 Eclipse 的 Variables 窗口看到圖 3 所示的輸出:
從圖 3 可以看出,此時的 addresses 屬性已經被初始化了,集合里包含了 2 個 Address 對象,這正是 Person 實體所關聯的兩個 Address 對象。
通過上面介紹可以看出,Hibernate 對于 Set 屬性延遲加載關鍵就在于 PersistentSet 實現類。在延遲加載時,開始 PersistentSet 集合里并不持有任何元素。但 PersistentSet 會持有一個 Hibernate Session,它可以保證當程序需要訪問該集合時“立即”去加載數據記錄,并裝入集合元素。
與 PersistentSet 實現類類似的是,Hibernate 還提供了 PersistentList、PersistentMap、PersistentSortedMap、PersistentSortedSet 等實現類,它們的功能與 PersistentSet 的功能大致類似。
熟悉 Hibernate 集合屬性讀者應該記得:Hibernate 要求聲明集合屬性只能用 Set、List、Map、SortedSet、SortedMap 等接口,而不能用 HashSet、ArrayList、HashMap、TreeSet、TreeMap 等實現類,其原因就是因為 Hibernate 需要對集合屬性進行延遲加載,而 Hibernate 的延遲加載是依靠 PersistentSet、PersistentList、PersistentMap、PersistentSortedMap、PersistentSortedSet 來完成的——也就是說,Hibernate 底層需要使用自己的集合實現類來完成延遲加載,因此它要求開發者必須用集合接口、而不是集合實現類來聲明集合屬性。
Hibernate 對集合屬性默認采用延遲加載,在某些特殊的情況下,為 <set.../>、<list.../>、<map.../> 等元素設置 lazy="false"屬性來取消延遲加載。
關聯實體的延遲加載
默認情況下,Hibernate 也會采用延遲加載來加載關聯實體,不管是一對多關聯、還是一對一關聯、多對多關聯,Hibernate 默認都會采用延遲加載。
對于關聯實體,可以將其分為兩種情況:
<li>關聯實體是多個實體時(包括一對多、多對多):此時關聯實體將以集合的形式存在,Hibernate 將使用 PersistentSet、PersistentList、PersistentMap、PersistentSortedMap、PersistentSortedSet 等集合來管理延遲加載的實體。這就是前面所介紹的情形。
<li>關聯實體是單個實體時(包括一對一、多對一):當 Hibernate 加載某個實體時,延遲的關聯實體將是一個動態生成代理對象。
當關聯實體是單個實體時,也就是使用 <many-to-one.../> 或 <one-to-one.../> 映射關聯實體的情形,這兩個元素也可通過 lazy 屬性來指定延遲加載。
下面例子把 Address 類也映射成持久化類,此時 Address 類也變成實體類,Person 實體與 Address 實體形成一對多的雙向關聯。此時的映射文件代碼如下:
<?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">
<!-- 映射標識屬性 id -->
<id name="id" column="person_id">
<!-- 定義主鍵生成器策略 -->
<generator class="identity"/>
</id>
<!-- 用于映射普通屬性 -->
<property name="name" type="string"/>
<property name="age" type="int"/>
<!-- 映射集合屬性,集合元素是其他持久化實體
沒有指定 cascade 屬性,指定不控制關聯關系 -->
<set name="addresses" inverse="true">
<!-- 指定關聯的外鍵列 -->
<key column="person_id"/>
<!-- 用以映射到關聯類屬性 -->
<one-to-many class="Address"/>
</set>
</class>
<!-- 映射 Address 持久化類 -->
<class name="Address" table="address_inf">
<!-- 映射標識屬性 addressId -->
<id name="addressId" column="address_id">
<!-- 指定主鍵生成器策略 -->
<generator class="identity"/>
</id>
<!-- 映射普通屬性 detail -->
<property name="detail"/>
<!-- 映射普通屬性 zip -->
<property name="zip"/>
<!-- 必須指定列名為 person_id,
與關聯實體中 key 元素的 column 屬性值相同 -->
<many-to-one name="person" class="Person"
column="person_id" not-null="true"/>
</class>
</hibernate-mapping>
接下來程序通過如下代碼片段來加載 ID 為 1 的 Person 實體:
// 打開上下文相關的 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 實體時對其關聯實體的處理,我們在 <1>號代碼處設置一個斷點,在 Eclipse 中進行 Debug,此時可以看到 Eclipse 的 Console 窗口輸出如下 SQL 語句:
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 語句不難看出,Hibernate 加載 Address 實體對應的數據表抓取記錄,并未從 Person 實體對應的數據表中抓取記錄,這是延遲加載發揮了作用。
從 Eclipse 的 Variables 窗口看到如圖 4 所示的輸出:
從圖 4 可以清楚地看到,此時 Address 實體所關聯的 Person 實體并不是 Person 對象,而是一個 Person_$$_javassist_0 類的實例,這個類是 Hibernate 使用 Javassist 項目動態生成的代理類——當 Hibernate 延遲加載關聯實體時,將會采用 Javassist 生成一個動態代理對象,這個代理對象將負責代理“暫未加載”的關聯實體。
只要應用程序需要使用“暫未加載”的關聯實體,Person_$$_javassist_0 代理對象會負責去加載真正的關聯實體,并返回實際的關聯實體——這就是最典型的代理模式。
單擊圖 4 所示 Variables 窗口中的 person 屬性(也就是在調試模式下強行使用 person 屬性),此時看到 Eclipse 的 Console 窗口輸出如下的 SQL 語句:
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 語句就是去抓取“延遲加載”的關聯實體的語句。此時可以看到 Variables 窗口輸出圖 5 所示的結果:
Hibernate 采用“延遲加載”管理關聯實體的模式,其實就在加載主實體時,并未真正去抓取關聯實體對應數據,而只是動態地生成一個對象作為關聯實體的代理。當應用程序真正需要使用關聯實體時,代理對象會負責從底層數據庫抓取記錄,并初始化真正的關聯實體。
在 Hibernate 的延遲加載中,客戶端程序開始獲取的只是一個動態生成的代理對象,而真正的實體則委托給代理對象來管理——這就是典型的代理模式。